Fix for own error with updating view2d for NDOF
[blender.git] / source / blender / editors / interface / view2d_ops.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version. 
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2008 Blender Foundation.
19  * All rights reserved.
20  * 
21  * Contributor(s): Blender Foundation, Joshua Leung
22  *
23  * ***** END GPL LICENSE BLOCK *****
24  */
25
26 /** \file blender/editors/interface/view2d_ops.c
27  *  \ingroup edinterface
28  */
29
30
31 #include <math.h>
32
33 #include "MEM_guardedalloc.h"
34
35 #include "DNA_userdef_types.h"
36
37 #include "BLI_blenlib.h"
38 #include "BLI_utildefines.h"
39 #include "BLI_math_base.h"
40
41 #include "BKE_context.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 "ED_screen.h"
50
51 #include "UI_view2d.h"
52 #include "UI_interface.h"
53
54 #include "PIL_time.h" /* USER_ZOOM_CONT */
55
56 static int view2d_poll(bContext *C)
57 {
58         ARegion *ar = CTX_wm_region(C);
59
60         return (ar != NULL) && (ar->v2d.flag & V2D_IS_INITIALISED);
61 }
62
63 /* ********************************************************* */
64 /* VIEW PANNING OPERATOR                                                                 */
65
66 /*  This group of operators come in several forms:
67  *              1) Modal 'dragging' with MMB - where movement of mouse dictates amount to pan view by
68  *              2) Scrollwheel 'steps' - rolling mousewheel by one step moves view by predefined amount
69  *
70  *      In order to make sure this works, each operator must define the following RNA-Operator Props:
71  *              deltax, deltay  - define how much to move view by (relative to zoom-correction factor)
72  */
73
74 /* ------------------ Shared 'core' stuff ---------------------- */
75  
76 /* temp customdata for operator */
77 typedef struct v2dViewPanData {
78         bScreen *sc;            /* screen where view pan was initiated */
79         ScrArea *sa;            /* area where view pan was initiated */
80         ARegion *ar;            /* region where view pan was initiated */
81         View2D *v2d;            /* view2d we're operating in */
82
83         float facx, facy;       /* amount to move view relative to zoom */
84
85         /* options for version 1 */
86         int startx, starty;     /* mouse x/y values in window when operator was initiated */
87         int lastx, lasty;       /* previous x/y values of mouse in window */
88         int invoke_event;       /* event starting pan, for modal exit */
89         
90         short in_scroller;      /* for MMB in scrollers (old feature in past, but now not that useful) */
91 } v2dViewPanData;
92  
93 /* initialize panning customdata */
94 static int view_pan_init(bContext *C, wmOperator *op)
95 {
96         ARegion *ar = CTX_wm_region(C);
97         v2dViewPanData *vpd;
98         View2D *v2d;
99         float winx, winy;
100         
101         /* regions now have v2d-data by default, so check for region */
102         if (ar == NULL)
103                 return 0;
104                 
105         /* check if panning is allowed at all */
106         v2d = &ar->v2d;
107         if ((v2d->keepofs & V2D_LOCKOFS_X) && (v2d->keepofs & V2D_LOCKOFS_Y))
108                 return 0;
109         
110         /* set custom-data for operator */
111         vpd = MEM_callocN(sizeof(v2dViewPanData), "v2dViewPanData");
112         op->customdata = vpd;
113         
114         /* set pointers to owners */
115         vpd->sc = CTX_wm_screen(C);
116         vpd->sa = CTX_wm_area(C);
117         vpd->v2d = v2d;
118         vpd->ar = ar;
119         
120         /* calculate translation factor - based on size of view */
121         winx = (float)(BLI_rcti_size_x(&ar->winrct) + 1);
122         winy = (float)(BLI_rcti_size_y(&ar->winrct) + 1);
123         vpd->facx = (BLI_rctf_size_x(&v2d->cur)) / winx;
124         vpd->facy = (BLI_rctf_size_y(&v2d->cur)) / winy;
125         
126         return 1;
127 }
128
129 static int view_pan_poll(bContext *C)
130 {
131         ARegion *ar = CTX_wm_region(C);
132         View2D *v2d;
133
134         /* check if there's a region in context to work with */
135         if (ar == NULL)
136                 return 0;
137         v2d = &ar->v2d;
138
139         /* check that 2d-view can pan */
140         if ((v2d->keepofs & V2D_LOCKOFS_X) && (v2d->keepofs & V2D_LOCKOFS_Y))
141                 return 0;
142
143         /* view can pan */
144         return 1;
145 }
146
147 /* apply transform to view (i.e. adjust 'cur' rect) */
148 static void view_pan_apply_ex(bContext *C, v2dViewPanData *vpd, float dx, float dy)
149 {
150         View2D *v2d = vpd->v2d;
151         
152         /* calculate amount to move view by */
153         dx *= vpd->facx;
154         dy *= vpd->facy;
155         
156         /* only move view on an axis if change is allowed */
157         if ((v2d->keepofs & V2D_LOCKOFS_X) == 0) {
158                 v2d->cur.xmin += dx;
159                 v2d->cur.xmax += dx;
160         }
161         if ((v2d->keepofs & V2D_LOCKOFS_Y) == 0) {
162                 v2d->cur.ymin += dy;
163                 v2d->cur.ymax += dy;
164         }
165         
166         /* validate that view is in valid configuration after this operation */
167         UI_view2d_curRect_validate(v2d);
168         
169         /* request updates to be done... */
170         ED_region_tag_redraw(vpd->ar);
171         WM_event_add_mousemove(C);
172         
173         UI_view2d_sync(vpd->sc, vpd->sa, v2d, V2D_LOCK_COPY);
174         
175         /* exceptions */
176         if (vpd->sa->spacetype == SPACE_OUTLINER) {
177                 /* don't rebuild full tree, since we're just changing our view */
178                 SpaceOops *soops = vpd->sa->spacedata.first;
179                 soops->storeflag |= SO_TREESTORE_REDRAW;
180         }
181 }
182
183 static void view_pan_apply(bContext *C, wmOperator *op)
184 {
185         v2dViewPanData *vpd = op->customdata;
186
187         view_pan_apply_ex(C, vpd,
188                           RNA_int_get(op->ptr, "deltax"),
189                           RNA_int_get(op->ptr, "deltay"));
190
191 }
192
193 /* cleanup temp customdata  */
194 static void view_pan_exit(wmOperator *op)
195 {
196         if (op->customdata) {
197                 MEM_freeN(op->customdata);
198                 op->customdata = NULL;
199         }
200
201  
202 /* ------------------ Modal Drag Version (1) ---------------------- */
203
204 /* for 'redo' only, with no user input */
205 static int view_pan_exec(bContext *C, wmOperator *op)
206 {
207         if (!view_pan_init(C, op))
208                 return OPERATOR_CANCELLED;
209         
210         view_pan_apply(C, op);
211         view_pan_exit(op);
212         return OPERATOR_FINISHED;
213 }
214
215 /* set up modal operator and relevant settings */
216 static int view_pan_invoke(bContext *C, wmOperator *op, const wmEvent *event)
217 {
218         wmWindow *window = CTX_wm_window(C);
219         v2dViewPanData *vpd;
220         View2D *v2d;
221         
222         /* set up customdata */
223         if (!view_pan_init(C, op))
224                 return OPERATOR_PASS_THROUGH;
225         
226         vpd = op->customdata;
227         v2d = vpd->v2d;
228         
229         /* set initial settings */
230         vpd->startx = vpd->lastx = event->x;
231         vpd->starty = vpd->lasty = event->y;
232         vpd->invoke_event = event->type;
233         
234         if (event->type == MOUSEPAN) {
235                 RNA_int_set(op->ptr, "deltax", event->prevx - event->x);
236                 RNA_int_set(op->ptr, "deltay", event->prevy - event->y);
237                 
238                 view_pan_apply(C, op);
239                 view_pan_exit(op);
240                 return OPERATOR_FINISHED;
241         }
242         
243         RNA_int_set(op->ptr, "deltax", 0);
244         RNA_int_set(op->ptr, "deltay", 0);
245         
246         if (v2d->keepofs & V2D_LOCKOFS_X)
247                 WM_cursor_modal_set(window, BC_NS_SCROLLCURSOR);
248         else if (v2d->keepofs & V2D_LOCKOFS_Y)
249                 WM_cursor_modal_set(window, BC_EW_SCROLLCURSOR);
250         else
251                 WM_cursor_modal_set(window, BC_NSEW_SCROLLCURSOR);
252         
253         /* add temp handler */
254         WM_event_add_modal_handler(C, op);
255
256         return OPERATOR_RUNNING_MODAL;
257 }
258
259 /* handle user input - calculations of mouse-movement need to be done here, not in the apply callback! */
260 static int view_pan_modal(bContext *C, wmOperator *op, const wmEvent *event)
261 {
262         v2dViewPanData *vpd = op->customdata;
263         
264         /* execute the events */
265         switch (event->type) {
266                 case MOUSEMOVE:
267                 {
268                         /* calculate new delta transform, then store mouse-coordinates for next-time */
269                         RNA_int_set(op->ptr, "deltax", (vpd->lastx - event->x));
270                         RNA_int_set(op->ptr, "deltay", (vpd->lasty - event->y));
271                         
272                         vpd->lastx = event->x;
273                         vpd->lasty = event->y;
274                         
275                         view_pan_apply(C, op);
276                         break;
277                 }
278                         /* XXX - Mode switching isn't implemented. See comments in 36818.
279                          * switch to zoom */
280 #if 0
281                 case LEFTMOUSE:
282                         if (event->val == KM_PRESS) {
283                                 /* calculate overall delta mouse-movement for redo */
284                                 RNA_int_set(op->ptr, "deltax", (vpd->startx - vpd->lastx));
285                                 RNA_int_set(op->ptr, "deltay", (vpd->starty - vpd->lasty));
286                                 
287                                 view_pan_exit(op);
288                                 WM_cursor_modal_restore(CTX_wm_window(C));
289                                 WM_operator_name_call(C, "VIEW2D_OT_zoom", WM_OP_INVOKE_DEFAULT, NULL);
290                                 return OPERATOR_FINISHED;
291                         }
292 #endif
293                 default:
294                         if (event->type == vpd->invoke_event || event->type == ESCKEY) {
295                                 if (event->val == KM_RELEASE) {
296                                         /* calculate overall delta mouse-movement for redo */
297                                         RNA_int_set(op->ptr, "deltax", (vpd->startx - vpd->lastx));
298                                         RNA_int_set(op->ptr, "deltay", (vpd->starty - vpd->lasty));
299                                         
300                                         view_pan_exit(op);
301                                         WM_cursor_modal_restore(CTX_wm_window(C));
302                                         
303                                         return OPERATOR_FINISHED;
304                                 }
305                         }
306                         break;
307         }
308
309         return OPERATOR_RUNNING_MODAL;
310 }
311
312 static void view_pan_cancel(bContext *UNUSED(C), wmOperator *op)
313 {
314         view_pan_exit(op);
315 }
316
317 static void VIEW2D_OT_pan(wmOperatorType *ot)
318 {
319         /* identifiers */
320         ot->name = "Pan View";
321         ot->description = "Pan the view";
322         ot->idname = "VIEW2D_OT_pan";
323         
324         /* api callbacks */
325         ot->exec = view_pan_exec;
326         ot->invoke = view_pan_invoke;
327         ot->modal = view_pan_modal;
328         ot->cancel = view_pan_cancel;
329         
330         /* operator is modal */
331         ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_POINTER;
332         
333         /* rna - must keep these in sync with the other operators */
334         RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX);
335         RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX);
336 }
337
338 /* ------------------ Scrollwheel Versions (2) ---------------------- */
339
340 /* this operator only needs this single callback, where it calls the view_pan_*() methods */
341 static int view_scrollright_exec(bContext *C, wmOperator *op)
342 {
343         v2dViewPanData *vpd;
344         
345         /* initialize default settings (and validate if ok to run) */
346         if (!view_pan_init(C, op))
347                 return OPERATOR_PASS_THROUGH;
348                 
349         /* also, check if can pan in horizontal axis */
350         vpd = op->customdata;
351         if (vpd->v2d->keepofs & V2D_LOCKOFS_X) {
352                 view_pan_exit(op);
353                 return OPERATOR_PASS_THROUGH;
354         }
355         
356         /* set RNA-Props - only movement in positive x-direction */
357         RNA_int_set(op->ptr, "deltax", 20);
358         RNA_int_set(op->ptr, "deltay", 0);
359         
360         /* apply movement, then we're done */
361         view_pan_apply(C, op);
362         view_pan_exit(op);
363         
364         return OPERATOR_FINISHED;
365 }
366
367 static void VIEW2D_OT_scroll_right(wmOperatorType *ot)
368 {
369         /* identifiers */
370         ot->name = "Scroll Right";
371         ot->description = "Scroll the view right";
372         ot->idname = "VIEW2D_OT_scroll_right";
373         
374         /* api callbacks */
375         ot->exec = view_scrollright_exec;
376         
377         /* rna - must keep these in sync with the other operators */
378         RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX);
379         RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX);
380 }
381
382
383
384 /* this operator only needs this single callback, where it calls the view_pan_*() methods */
385 static int view_scrollleft_exec(bContext *C, wmOperator *op)
386 {
387         v2dViewPanData *vpd;
388         
389         /* initialize default settings (and validate if ok to run) */
390         if (!view_pan_init(C, op))
391                 return OPERATOR_PASS_THROUGH;
392                 
393         /* also, check if can pan in horizontal axis */
394         vpd = op->customdata;
395         if (vpd->v2d->keepofs & V2D_LOCKOFS_X) {
396                 view_pan_exit(op);
397                 return OPERATOR_PASS_THROUGH;
398         }
399         
400         /* set RNA-Props - only movement in negative x-direction */
401         RNA_int_set(op->ptr, "deltax", -20);
402         RNA_int_set(op->ptr, "deltay", 0);
403         
404         /* apply movement, then we're done */
405         view_pan_apply(C, op);
406         view_pan_exit(op);
407         
408         return OPERATOR_FINISHED;
409 }
410
411 static void VIEW2D_OT_scroll_left(wmOperatorType *ot)
412 {
413         /* identifiers */
414         ot->name = "Scroll Left";
415         ot->description = "Scroll the view left";
416         ot->idname = "VIEW2D_OT_scroll_left";
417         
418         /* api callbacks */
419         ot->exec = view_scrollleft_exec;
420         
421         /* rna - must keep these in sync with the other operators */
422         RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX);
423         RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX);
424 }
425
426
427 /* this operator only needs this single callback, where it calls the view_pan_*() methods */
428 static int view_scrolldown_exec(bContext *C, wmOperator *op)
429 {
430         v2dViewPanData *vpd;
431         
432         /* initialize default settings (and validate if ok to run) */
433         if (!view_pan_init(C, op))
434                 return OPERATOR_PASS_THROUGH;
435                 
436         /* also, check if can pan in vertical axis */
437         vpd = op->customdata;
438         if (vpd->v2d->keepofs & V2D_LOCKOFS_Y) {
439                 view_pan_exit(op);
440                 return OPERATOR_PASS_THROUGH;
441         }
442         
443         /* set RNA-Props */
444         RNA_int_set(op->ptr, "deltax", 0);
445         RNA_int_set(op->ptr, "deltay", -40);
446         
447         if (RNA_boolean_get(op->ptr, "page")) {
448                 ARegion *ar = CTX_wm_region(C);
449                 RNA_int_set(op->ptr, "deltay", ar->v2d.mask.ymin - ar->v2d.mask.ymax);
450         }
451         
452         /* apply movement, then we're done */
453         view_pan_apply(C, op);
454         view_pan_exit(op);
455         
456         return OPERATOR_FINISHED;
457 }
458
459 static void VIEW2D_OT_scroll_down(wmOperatorType *ot)
460 {
461         /* identifiers */
462         ot->name = "Scroll Down";
463         ot->description = "Scroll the view down";
464         ot->idname = "VIEW2D_OT_scroll_down";
465         
466         /* api callbacks */
467         ot->exec = view_scrolldown_exec;
468         
469         /* rna - must keep these in sync with the other operators */
470         RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX);
471         RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX);
472         RNA_def_boolean(ot->srna, "page", 0, "Page", "Scroll down one page");
473 }
474
475
476
477 /* this operator only needs this single callback, where it calls the view_pan_*() methods */
478 static int view_scrollup_exec(bContext *C, wmOperator *op)
479 {
480         v2dViewPanData *vpd;
481         
482         /* initialize default settings (and validate if ok to run) */
483         if (!view_pan_init(C, op))
484                 return OPERATOR_PASS_THROUGH;
485                 
486         /* also, check if can pan in vertical axis */
487         vpd = op->customdata;
488         if (vpd->v2d->keepofs & V2D_LOCKOFS_Y) {
489                 view_pan_exit(op);
490                 return OPERATOR_PASS_THROUGH;
491         }
492         
493         /* set RNA-Props */
494         RNA_int_set(op->ptr, "deltax", 0);
495         RNA_int_set(op->ptr, "deltay", 40);
496         
497         if (RNA_boolean_get(op->ptr, "page")) {
498                 ARegion *ar = CTX_wm_region(C);
499                 RNA_int_set(op->ptr, "deltay", BLI_rcti_size_y(&ar->v2d.mask));
500         }
501         
502         /* apply movement, then we're done */
503         view_pan_apply(C, op);
504         view_pan_exit(op);
505         
506         return OPERATOR_FINISHED;
507 }
508
509 static void VIEW2D_OT_scroll_up(wmOperatorType *ot)
510 {
511         /* identifiers */
512         ot->name = "Scroll Up";
513         ot->description = "Scroll the view up";
514         ot->idname = "VIEW2D_OT_scroll_up";
515         
516         /* api callbacks */
517         ot->exec = view_scrollup_exec;
518         
519         /* rna - must keep these in sync with the other operators */
520         RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX);
521         RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX);
522         RNA_def_boolean(ot->srna, "page", 0, "Page", "Scroll up one page");
523 }
524
525 /* ********************************************************* */
526 /* SINGLE-STEP VIEW ZOOMING OPERATOR                                             */
527
528 /* This group of operators come in several forms:
529  * 1) Scrollwheel 'steps' - rolling mousewheel by one step zooms view by predefined amount
530  * 2) Scrollwheel 'steps' + alt + ctrl/shift - zooms view on one axis only (ctrl=x, shift=y)  // XXX this could be implemented...
531  * 3) Pad +/- Keys - pressing each key moves the zooms the view by a predefined amount
532  *
533  * In order to make sure this works, each operator must define the following RNA-Operator Props:
534  * zoomfacx, zoomfacy - These two zoom factors allow for non-uniform scaling.
535  *                      It is safe to scale by 0, as these factors are used to determine
536  *                      amount to enlarge 'cur' by
537  */
538
539 /* ------------------ 'Shared' stuff ------------------------ */
540
541 /* temp customdata for operator */
542 typedef struct v2dViewZoomData {
543         View2D *v2d;            /* view2d we're operating in */
544         ARegion *ar;
545
546         /* needed for continuous zoom */
547         wmTimer *timer;
548         double timer_lastdraw;
549
550         int lastx, lasty;       /* previous x/y values of mouse in window */
551         int invoke_event;       /* event type that invoked, for modal exits */
552         float dx, dy;           /* running tally of previous delta values (for obtaining final zoom) */
553         float mx_2d, my_2d;     /* initial mouse location in v2d coords */
554 } v2dViewZoomData;
555
556 /**
557  * Clamp by convention rather then locking flags,
558  * for ndof and +/- keys
559  */
560 static void view_zoom_axis_lock_defaults(bContext *C, bool r_do_zoom_xy[2])
561 {
562         ScrArea *sa = CTX_wm_area(C);
563
564         r_do_zoom_xy[0] = true;
565         r_do_zoom_xy[1] = true;
566
567         /* default not to zoom the sequencer vertically */
568         if (sa && sa->spacetype == SPACE_SEQ) {
569                 ARegion *ar = CTX_wm_region(C);
570
571                 if (ar && ar->regiontype != RGN_TYPE_PREVIEW)
572                         r_do_zoom_xy[1] = false;
573         }
574 }
575
576 /* initialize panning customdata */
577 static int view_zoomdrag_init(bContext *C, wmOperator *op)
578 {
579         ARegion *ar = CTX_wm_region(C);
580         v2dViewZoomData *vzd;
581         View2D *v2d;
582         
583         /* regions now have v2d-data by default, so check for region */
584         if (ar == NULL)
585                 return 0;
586         v2d = &ar->v2d;
587         
588         /* check that 2d-view is zoomable */
589         if ((v2d->keepzoom & V2D_LOCKZOOM_X) && (v2d->keepzoom & V2D_LOCKZOOM_Y))
590                 return 0;
591         
592         /* set custom-data for operator */
593         vzd = MEM_callocN(sizeof(v2dViewZoomData), "v2dViewZoomData");
594         op->customdata = vzd;
595         
596         /* set pointers to owners */
597         vzd->v2d = v2d;
598         vzd->ar = ar;
599         
600         return 1;
601 }
602
603 /* check if step-zoom can be applied */
604 static int view_zoom_poll(bContext *C)
605 {
606         ARegion *ar = CTX_wm_region(C);
607         View2D *v2d;
608         
609         /* check if there's a region in context to work with */
610         if (ar == NULL)
611                 return 0;
612         v2d = &ar->v2d;
613         
614         /* check that 2d-view is zoomable */
615         if ((v2d->keepzoom & V2D_LOCKZOOM_X) && (v2d->keepzoom & V2D_LOCKZOOM_Y))
616                 return 0;
617                 
618         /* view is zoomable */
619         return 1;
620 }
621  
622 /* apply transform to view (i.e. adjust 'cur' rect) */
623 static void view_zoomstep_apply_ex(bContext *C, v2dViewZoomData *vzd, const bool use_mousepos,
624                                    const float facx, const float facy)
625 {
626         ARegion *ar = CTX_wm_region(C);
627         View2D *v2d = &ar->v2d;
628         float dx, dy;
629
630         /* calculate amount to move view by, ensuring symmetry so the
631          * old zoom level is restored after zooming back the same amount 
632          */
633         if (facx >= 0.0f) {
634                 dx = BLI_rctf_size_x(&v2d->cur) * facx;
635                 dy = BLI_rctf_size_y(&v2d->cur) * facy;
636         }
637         else {
638                 dx = (BLI_rctf_size_x(&v2d->cur) / (1.0f + 2.0f * facx)) * facx;
639                 dy = (BLI_rctf_size_y(&v2d->cur) / (1.0f + 2.0f * facy)) * facy;
640         }
641
642         /* only resize view on an axis if change is allowed */
643         if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) {
644                 if (v2d->keepofs & V2D_LOCKOFS_X) {
645                         v2d->cur.xmax -= 2 * dx;
646                 }
647                 else if (v2d->keepofs & V2D_KEEPOFS_X) {
648                         if (v2d->align & V2D_ALIGN_NO_POS_X)
649                                 v2d->cur.xmin += 2 * dx;
650                         else
651                                 v2d->cur.xmax -= 2 * dx;
652                 }
653                 else {
654                         if (use_mousepos && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) {
655                                 float mval_fac = (vzd->mx_2d - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur);
656                                 float mval_faci = 1.0f - mval_fac;
657                                 float ofs = (mval_fac * dx) - (mval_faci * dx);
658                                 
659                                 v2d->cur.xmin += ofs + dx;
660                                 v2d->cur.xmax += ofs - dx;
661                         }
662                         else {
663                                 v2d->cur.xmin += dx;
664                                 v2d->cur.xmax -= dx;
665                         }
666                 }
667         }
668         if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) {
669                 if (v2d->keepofs & V2D_LOCKOFS_Y) {
670                         v2d->cur.ymax -= 2 * dy;
671                 }
672                 else if (v2d->keepofs & V2D_KEEPOFS_Y) {
673                         if (v2d->align & V2D_ALIGN_NO_POS_Y)
674                                 v2d->cur.ymin += 2 * dy;
675                         else
676                                 v2d->cur.ymax -= 2 * dy;
677                 }
678                 else {
679                         if (use_mousepos && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) {
680                                 float mval_fac = (vzd->my_2d - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur);
681                                 float mval_faci = 1.0f - mval_fac;
682                                 float ofs = (mval_fac * dy) - (mval_faci * dy);
683                                 
684                                 v2d->cur.ymin += ofs + dy;
685                                 v2d->cur.ymax += ofs - dy;
686                         }
687                         else {
688                                 v2d->cur.ymin += dy;
689                                 v2d->cur.ymax -= dy;
690                         }
691                 }
692         }
693
694         /* validate that view is in valid configuration after this operation */
695         UI_view2d_curRect_validate(v2d);
696
697         /* request updates to be done... */
698         ED_region_tag_redraw(vzd->ar);
699         UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
700 }
701
702 static void view_zoomstep_apply(bContext *C, wmOperator *op)
703 {
704         v2dViewZoomData *vzd = op->customdata;
705         view_zoomstep_apply_ex(C, vzd, true,
706                                RNA_float_get(op->ptr, "zoomfacx"),
707                                RNA_float_get(op->ptr, "zoomfacy"));
708 }
709
710 /* --------------- Individual Operators ------------------- */
711
712 /* cleanup temp customdata  */
713 static void view_zoomstep_exit(wmOperator *op)
714 {
715         UI_view2d_zoom_cache_reset();
716
717         if (op->customdata) {
718                 MEM_freeN(op->customdata);
719                 op->customdata = NULL;
720         }
721 }
722
723 /* this operator only needs this single callback, where it calls the view_zoom_*() methods */
724 static int view_zoomin_exec(bContext *C, wmOperator *op)
725 {
726         bool do_zoom_xy[2];
727
728         /* check that there's an active region, as View2D data resides there */
729         if (!view_zoom_poll(C))
730                 return OPERATOR_PASS_THROUGH;
731         
732
733         view_zoom_axis_lock_defaults(C, do_zoom_xy);
734
735         /* set RNA-Props - zooming in by uniform factor */
736         RNA_float_set(op->ptr, "zoomfacx", do_zoom_xy[0] ? 0.0375f : 0.0f);
737         RNA_float_set(op->ptr, "zoomfacy", do_zoom_xy[1] ? 0.0375f : 0.0f);
738         
739         /* apply movement, then we're done */
740         view_zoomstep_apply(C, op);
741         
742         view_zoomstep_exit(op);
743         
744         return OPERATOR_FINISHED;
745 }
746
747 static int view_zoomin_invoke(bContext *C, wmOperator *op, const wmEvent *event)
748 {
749         v2dViewZoomData *vzd;
750         
751         if (!view_zoomdrag_init(C, op))
752                 return OPERATOR_PASS_THROUGH;
753         
754         vzd = op->customdata;
755         
756         if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
757                 ARegion *ar = CTX_wm_region(C);
758                 
759                 /* store initial mouse position (in view space) */
760                 UI_view2d_region_to_view(&ar->v2d, 
761                                          event->mval[0], event->mval[1],
762                                          &vzd->mx_2d, &vzd->my_2d);
763         }
764         
765         return view_zoomin_exec(C, op);
766 }
767
768 static void VIEW2D_OT_zoom_in(wmOperatorType *ot)
769 {
770         /* identifiers */
771         ot->name = "Zoom In";
772         ot->description = "Zoom in the view";
773         ot->idname = "VIEW2D_OT_zoom_in";
774         
775         /* api callbacks */
776         ot->invoke = view_zoomin_invoke;
777         ot->exec = view_zoomin_exec;  // XXX, needs view_zoomdrag_init called first.
778         ot->poll = view_zoom_poll;
779         
780         /* rna - must keep these in sync with the other operators */
781         RNA_def_float(ot->srna, "zoomfacx", 0, -FLT_MAX, FLT_MAX, "Zoom Factor X", "", -FLT_MAX, FLT_MAX);
782         RNA_def_float(ot->srna, "zoomfacy", 0, -FLT_MAX, FLT_MAX, "Zoom Factor Y", "", -FLT_MAX, FLT_MAX);
783 }
784         
785 /* this operator only needs this single callback, where it calls the view_zoom_*() methods */
786 static int view_zoomout_exec(bContext *C, wmOperator *op)
787 {
788         bool do_zoom_xy[2];
789
790         /* check that there's an active region, as View2D data resides there */
791         if (!view_zoom_poll(C))
792                 return OPERATOR_PASS_THROUGH;
793         
794         view_zoom_axis_lock_defaults(C, do_zoom_xy);
795
796         /* set RNA-Props - zooming in by uniform factor */
797         RNA_float_set(op->ptr, "zoomfacx", do_zoom_xy[0] ? -0.0375f : 0.0f);
798         RNA_float_set(op->ptr, "zoomfacy", do_zoom_xy[1] ? -0.0375f : 0.0f);
799         
800         /* apply movement, then we're done */
801         view_zoomstep_apply(C, op);
802
803         view_zoomstep_exit(op);
804         
805         return OPERATOR_FINISHED;
806 }
807
808 static int view_zoomout_invoke(bContext *C, wmOperator *op, const wmEvent *event)
809 {
810         v2dViewZoomData *vzd;
811         
812         if (!view_zoomdrag_init(C, op))
813                 return OPERATOR_PASS_THROUGH;
814
815         vzd = op->customdata;
816         
817         if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
818                 ARegion *ar = CTX_wm_region(C);
819                 
820                 /* store initial mouse position (in view space) */
821                 UI_view2d_region_to_view(&ar->v2d, 
822                                          event->mval[0], event->mval[1],
823                                          &vzd->mx_2d, &vzd->my_2d);
824         }
825         
826         return view_zoomout_exec(C, op);
827 }
828
829 static void VIEW2D_OT_zoom_out(wmOperatorType *ot)
830 {
831         /* identifiers */
832         ot->name = "Zoom Out";
833         ot->description = "Zoom out the view";
834         ot->idname = "VIEW2D_OT_zoom_out";
835         
836         /* api callbacks */
837         ot->invoke = view_zoomout_invoke;
838 //      ot->exec = view_zoomout_exec; // XXX, needs view_zoomdrag_init called first.
839         ot->poll = view_zoom_poll;
840         
841         /* rna - must keep these in sync with the other operators */
842         RNA_def_float(ot->srna, "zoomfacx", 0, -FLT_MAX, FLT_MAX, "Zoom Factor X", "", -FLT_MAX, FLT_MAX);
843         RNA_def_float(ot->srna, "zoomfacy", 0, -FLT_MAX, FLT_MAX, "Zoom Factor Y", "", -FLT_MAX, FLT_MAX);
844 }
845
846 /* ********************************************************* */
847 /* DRAG-ZOOM OPERATOR                                                                    */
848
849 /*  MMB Drag - allows non-uniform scaling by dragging mouse
850  *
851  *      In order to make sure this works, each operator must define the following RNA-Operator Props:
852  *              deltax, deltay  - amounts to add to each side of the 'cur' rect
853  */
854
855 /* apply transform to view (i.e. adjust 'cur' rect) */
856 static void view_zoomdrag_apply(bContext *C, wmOperator *op)
857 {
858         v2dViewZoomData *vzd = op->customdata;
859         View2D *v2d = vzd->v2d;
860         float dx, dy;
861         
862         /* get amount to move view by */
863         dx = RNA_float_get(op->ptr, "deltax");
864         dy = RNA_float_get(op->ptr, "deltay");
865
866         if (U.uiflag & USER_ZOOM_INVERT) {
867                 dx *= -1;
868                 dy *= -1;
869         }
870         
871         /* continuous zoom shouldn't move that fast... */
872         if (U.viewzoom == USER_ZOOM_CONT) { // XXX store this setting as RNA prop?
873                 double time = PIL_check_seconds_timer();
874                 float time_step = (float)(time - vzd->timer_lastdraw);
875
876                 dx *= time_step * 0.5f;
877                 dy *= time_step * 0.5f;
878                 
879                 vzd->timer_lastdraw = time;
880         }
881
882         /* only move view on an axis if change is allowed */
883         if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) {
884                 if (v2d->keepofs & V2D_LOCKOFS_X) {
885                         v2d->cur.xmax -= 2 * dx;
886                 }
887                 else {
888                         if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
889                                 float mval_fac = (vzd->mx_2d - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur);
890                                 float mval_faci = 1.0f - mval_fac;
891                                 float ofs = (mval_fac * dx) - (mval_faci * dx);
892                                 
893                                 v2d->cur.xmin += ofs + dx;
894                                 v2d->cur.xmax += ofs - dx;
895                         }
896                         else {
897                                 v2d->cur.xmin += dx;
898                                 v2d->cur.xmax -= dx;
899                         }
900                 }
901         }
902         if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) {
903                 if (v2d->keepofs & V2D_LOCKOFS_Y) {
904                         v2d->cur.ymax -= 2 * dy;
905                 }
906                 else {
907                         if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
908                                 float mval_fac = (vzd->my_2d - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur);
909                                 float mval_faci = 1.0f - mval_fac;
910                                 float ofs = (mval_fac * dy) - (mval_faci * dy);
911                                 
912                                 v2d->cur.ymin += ofs + dy;
913                                 v2d->cur.ymax += ofs - dy;
914                         }
915                         else {
916                                 v2d->cur.ymin += dy;
917                                 v2d->cur.ymax -= dy;
918                         }
919                 }
920         }
921         
922         /* validate that view is in valid configuration after this operation */
923         UI_view2d_curRect_validate(v2d);
924         
925         /* request updates to be done... */
926         ED_region_tag_redraw(vzd->ar);
927         UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
928 }
929
930 /* cleanup temp customdata  */
931 static void view_zoomdrag_exit(bContext *C, wmOperator *op)
932 {
933         UI_view2d_zoom_cache_reset();
934
935         if (op->customdata) {
936                 v2dViewZoomData *vzd = op->customdata;
937                 
938                 if (vzd->timer)
939                         WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), vzd->timer);
940                 
941                 MEM_freeN(op->customdata);
942                 op->customdata = NULL;
943         }
944
945
946 static void view_zoomdrag_cancel(bContext *C, wmOperator *op)
947 {
948         view_zoomdrag_exit(C, op);
949 }
950
951 /* for 'redo' only, with no user input */
952 static int view_zoomdrag_exec(bContext *C, wmOperator *op)
953 {
954         if (!view_zoomdrag_init(C, op))
955                 return OPERATOR_PASS_THROUGH;
956         
957         view_zoomdrag_apply(C, op);
958         view_zoomdrag_exit(C, op);
959         return OPERATOR_FINISHED;
960 }
961
962 /* set up modal operator and relevant settings */
963 static int view_zoomdrag_invoke(bContext *C, wmOperator *op, const wmEvent *event)
964 {
965         wmWindow *window = CTX_wm_window(C);
966         v2dViewZoomData *vzd;
967         View2D *v2d;
968         
969         /* set up customdata */
970         if (!view_zoomdrag_init(C, op))
971                 return OPERATOR_PASS_THROUGH;
972         
973         vzd = op->customdata;
974         v2d = vzd->v2d;
975         
976         if (event->type == MOUSEZOOM || event->type == MOUSEPAN) {
977                 float dx, dy, fac;
978                 
979                 vzd->lastx = event->prevx;
980                 vzd->lasty = event->prevy;
981                 
982                 /* As we have only 1D information (magnify value), feed both axes
983                  * with magnify information that is stored in x axis 
984                  */
985                 fac = 0.01f * (event->prevx - event->x);
986                 dx = fac * BLI_rctf_size_x(&v2d->cur) / 10.0f;
987                 if (event->type == MOUSEPAN)
988                         fac = 0.01f * (event->prevy - event->y);
989                 dy = fac * BLI_rctf_size_y(&v2d->cur) / 10.0f;
990
991                 /* support trackpad zoom to always zoom entirely - the v2d code uses portrait or landscape exceptions */
992                 if (v2d->keepzoom & V2D_KEEPASPECT) {
993                         if (fabsf(dx) > fabsf(dy))
994                                 dy = dx;
995                         else
996                                 dx = dy;
997                 }
998                 RNA_float_set(op->ptr, "deltax", dx);
999                 RNA_float_set(op->ptr, "deltay", dy);
1000                 
1001                 view_zoomdrag_apply(C, op);
1002                 view_zoomdrag_exit(C, op);
1003                 return OPERATOR_FINISHED;
1004         }
1005         
1006         /* set initial settings */
1007         vzd->lastx = event->x;
1008         vzd->lasty = event->y;
1009         RNA_float_set(op->ptr, "deltax", 0);
1010         RNA_float_set(op->ptr, "deltay", 0);
1011         
1012         /* for modal exit test */
1013         vzd->invoke_event = event->type;
1014         
1015         if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
1016                 ARegion *ar = CTX_wm_region(C);
1017                 
1018                 /* store initial mouse position (in view space) */
1019                 UI_view2d_region_to_view(&ar->v2d, 
1020                                          event->mval[0], event->mval[1],
1021                                          &vzd->mx_2d, &vzd->my_2d);
1022         }
1023
1024         if (v2d->keepofs & V2D_LOCKOFS_X)
1025                 WM_cursor_modal_set(window, BC_NS_SCROLLCURSOR);
1026         else if (v2d->keepofs & V2D_LOCKOFS_Y)
1027                 WM_cursor_modal_set(window, BC_EW_SCROLLCURSOR);
1028         else
1029                 WM_cursor_modal_set(window, BC_NSEW_SCROLLCURSOR);
1030         
1031         /* add temp handler */
1032         WM_event_add_modal_handler(C, op);
1033
1034         if (U.viewzoom == USER_ZOOM_CONT) {
1035                 /* needs a timer to continue redrawing */
1036                 vzd->timer = WM_event_add_timer(CTX_wm_manager(C), CTX_wm_window(C), TIMER, 0.01f);
1037                 vzd->timer_lastdraw = PIL_check_seconds_timer();
1038         }
1039
1040         return OPERATOR_RUNNING_MODAL;
1041 }
1042
1043 /* handle user input - calculations of mouse-movement need to be done here, not in the apply callback! */
1044 static int view_zoomdrag_modal(bContext *C, wmOperator *op, const wmEvent *event)
1045 {
1046         v2dViewZoomData *vzd = op->customdata;
1047         View2D *v2d = vzd->v2d;
1048         
1049         /* execute the events */
1050         if (event->type == TIMER && event->customdata == vzd->timer) {
1051                 view_zoomdrag_apply(C, op);
1052         }
1053         else if (event->type == MOUSEMOVE) {
1054                 float dx, dy;
1055                 
1056                 /* calculate new delta transform, based on zooming mode */
1057                 if (U.viewzoom == USER_ZOOM_SCALE) {
1058                         /* 'scale' zooming */
1059                         float dist;
1060                         
1061                         /* x-axis transform */
1062                         dist = BLI_rcti_size_x(&v2d->mask) / 2.0f;
1063                         dx = 1.0f - (fabsf(vzd->lastx - vzd->ar->winrct.xmin - dist) + 2.0f) / (fabsf(event->mval[0] - dist) + 2.0f);
1064                         dx *= 0.5f * BLI_rctf_size_x(&v2d->cur);
1065                         
1066                         /* y-axis transform */
1067                         dist = BLI_rcti_size_y(&v2d->mask) / 2.0f;
1068                         dy = 1.0f - (fabsf(vzd->lasty - vzd->ar->winrct.ymin - dist) + 2.0f) / (fabsf(event->mval[1] - dist) + 2.0f);
1069                         dy *= 0.5f * BLI_rctf_size_y(&v2d->cur);
1070                 }
1071                 else {
1072                         /* 'continuous' or 'dolly' */
1073                         float fac, zoomfac = 0.01f;
1074                         
1075                         /* some view2d's (graph) don't have min/max zoom, or extreme ones */
1076                         if (v2d->maxzoom > 0.0f)
1077                                 zoomfac = CLAMPIS(0.001f * v2d->maxzoom, 0.001f, 0.01f);
1078                         
1079                         /* x-axis transform */
1080                         fac = zoomfac * (event->x - vzd->lastx);
1081                         dx = fac * BLI_rctf_size_x(&v2d->cur);
1082                         
1083                         /* y-axis transform */
1084                         fac = zoomfac * (event->y - vzd->lasty);
1085                         dy = fac * BLI_rctf_size_y(&v2d->cur);
1086                         
1087                 }
1088                 
1089                 /* support zoom to always zoom entirely - the v2d code uses portrait or landscape exceptions */
1090                 if (v2d->keepzoom & V2D_KEEPASPECT) {
1091                         if (fabsf(dx) > fabsf(dy))
1092                                 dy = dx;
1093                         else
1094                                 dx = dy;
1095                 }
1096                 
1097                 /* set transform amount, and add current deltas to stored total delta (for redo) */
1098                 RNA_float_set(op->ptr, "deltax", dx);
1099                 RNA_float_set(op->ptr, "deltay", dy);
1100
1101                 vzd->dx += dx;
1102                 vzd->dy += dy;
1103                 
1104                 /* store mouse coordinates for next time, if not doing continuous zoom
1105                  *      - continuous zoom only depends on distance of mouse to starting point to determine rate of change
1106                  */
1107                 if (U.viewzoom != USER_ZOOM_CONT) { // XXX store this setting as RNA prop?
1108                         vzd->lastx = event->x;
1109                         vzd->lasty = event->y;
1110                 }
1111                 
1112                 /* apply zooming */
1113                 view_zoomdrag_apply(C, op);
1114         }
1115         else if (event->type == vzd->invoke_event || event->type == ESCKEY) {
1116                 if (event->val == KM_RELEASE) {
1117                         
1118                         /* for redo, store the overall deltas - need to respect zoom-locks here... */
1119                         if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0)
1120                                 RNA_float_set(op->ptr, "deltax", vzd->dx);
1121                         else
1122                                 RNA_float_set(op->ptr, "deltax", 0);
1123                                 
1124                         if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0)
1125                                 RNA_float_set(op->ptr, "deltay", vzd->dy);
1126                         else
1127                                 RNA_float_set(op->ptr, "deltay", 0);
1128                         
1129                         /* free customdata */
1130                         view_zoomdrag_exit(C, op);
1131                         WM_cursor_modal_restore(CTX_wm_window(C));
1132                         
1133                         return OPERATOR_FINISHED;
1134                 }
1135         }
1136
1137         return OPERATOR_RUNNING_MODAL;
1138 }
1139
1140 static void VIEW2D_OT_zoom(wmOperatorType *ot)
1141 {
1142         /* identifiers */
1143         ot->name = "Zoom 2D View";
1144         ot->description = "Zoom in/out the view";
1145         ot->idname = "VIEW2D_OT_zoom";
1146         
1147         /* api callbacks */
1148         ot->exec = view_zoomdrag_exec;
1149         ot->invoke = view_zoomdrag_invoke;
1150         ot->modal = view_zoomdrag_modal;
1151         ot->cancel = view_zoomdrag_cancel;
1152         
1153         ot->poll = view_zoom_poll;
1154         
1155         /* operator is repeatable */
1156         ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_POINTER;
1157         
1158         /* rna - must keep these in sync with the other operators */
1159         RNA_def_float(ot->srna, "deltax", 0, -FLT_MAX, FLT_MAX, "Delta X", "", -FLT_MAX, FLT_MAX);
1160         RNA_def_float(ot->srna, "deltay", 0, -FLT_MAX, FLT_MAX, "Delta Y", "", -FLT_MAX, FLT_MAX);
1161 }
1162
1163 /* ********************************************************* */
1164 /* BORDER-ZOOM */
1165
1166 /* The user defines a rect using standard borderselect tools, and we use this rect to 
1167  * define the new zoom-level of the view in the following ways:
1168  *      1) LEFTMOUSE - zoom in to view
1169  *      2) RIGHTMOUSE - zoom out of view
1170  *
1171  * Currently, these key mappings are hardcoded, but it shouldn't be too important to
1172  * have custom keymappings for this...
1173  */
1174  
1175 static int view_borderzoom_exec(bContext *C, wmOperator *op)
1176 {
1177         ARegion *ar = CTX_wm_region(C);
1178         View2D *v2d = &ar->v2d;
1179         rctf rect;
1180         rctf cur_new = v2d->cur;
1181         int gesture_mode;
1182         const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
1183         
1184         /* convert coordinates of rect to 'tot' rect coordinates */
1185         UI_view2d_region_to_view(v2d, RNA_int_get(op->ptr, "xmin"), RNA_int_get(op->ptr, "ymin"), &rect.xmin, &rect.ymin);
1186         UI_view2d_region_to_view(v2d, RNA_int_get(op->ptr, "xmax"), RNA_int_get(op->ptr, "ymax"), &rect.xmax, &rect.ymax);
1187         
1188         /* check if zooming in/out view */
1189         gesture_mode = RNA_int_get(op->ptr, "gesture_mode");
1190         
1191         if (gesture_mode == GESTURE_MODAL_IN) {
1192                 /* zoom in: 
1193                  *      - 'cur' rect will be defined by the coordinates of the border region 
1194                  *      - just set the 'cur' rect to have the same coordinates as the border region
1195                  *        if zoom is allowed to be changed
1196                  */
1197                 if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) {
1198                         cur_new.xmin = rect.xmin;
1199                         cur_new.xmax = rect.xmax;
1200                 }
1201                 if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) {
1202                         cur_new.ymin = rect.ymin;
1203                         cur_new.ymax = rect.ymax;
1204                 }
1205         }
1206         else { /* if (gesture_mode == GESTURE_MODAL_OUT) */
1207
1208                 /* zoom out:
1209                  *      - the current 'cur' rect coordinates are going to end up where the 'rect' ones are,
1210                  *        but the 'cur' rect coordinates will need to be adjusted to take in more of the view
1211                  *      - calculate zoom factor, and adjust using center-point
1212                  */
1213                 float zoom, center, size;
1214                 
1215                 /* TODO: is this zoom factor calculation valid? It seems to produce same results every time... */
1216                 if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) {
1217                         size = BLI_rctf_size_x(&cur_new);
1218                         zoom = size / BLI_rctf_size_x(&rect);
1219                         center = BLI_rctf_cent_x(&cur_new);
1220                         
1221                         cur_new.xmin = center - (size * zoom);
1222                         cur_new.xmax = center + (size * zoom);
1223                 }
1224                 if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) {
1225                         size = BLI_rctf_size_y(&cur_new);
1226                         zoom = size / BLI_rctf_size_y(&rect);
1227                         center = BLI_rctf_cent_y(&cur_new);
1228                         
1229                         cur_new.ymin = center - (size * zoom);
1230                         cur_new.ymax = center + (size * zoom);
1231                 }
1232         }
1233         
1234         UI_view2d_smooth_view(C, ar, &cur_new, smooth_viewtx);
1235         
1236         return OPERATOR_FINISHED;
1237
1238
1239 static void VIEW2D_OT_zoom_border(wmOperatorType *ot)
1240 {
1241         /* identifiers */
1242         ot->name = "Zoom to Border";
1243         ot->description = "Zoom in the view to the nearest item contained in the border";
1244         ot->idname = "VIEW2D_OT_zoom_border";
1245         
1246         /* api callbacks */
1247         ot->invoke = WM_border_select_invoke;
1248         ot->exec = view_borderzoom_exec;
1249         ot->modal = WM_border_select_modal;
1250         ot->cancel = WM_border_select_cancel;
1251         
1252         ot->poll = view_zoom_poll;
1253         
1254         /* rna */
1255         WM_operator_properties_gesture_border(ot, false);
1256 }
1257
1258
1259 static int view2d_ndof_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1260 {
1261         if (event->type != NDOF_MOTION) {
1262                 return OPERATOR_CANCELLED;
1263         }
1264         else {
1265                 const wmNDOFMotionData *ndof = event->customdata;
1266
1267                 /* tune these until it feels right */
1268                 const float zoom_sensitivity = 0.5f;
1269                 const float speed = 10.0f;  /* match view3d ortho */
1270                 const bool has_translate = (ndof->tvec[0] && ndof->tvec[1]) && view_pan_poll(C);
1271                 const bool has_zoom = (ndof->tvec[2] != 0.0f) && view_zoom_poll(C);
1272
1273                 if (has_translate) {
1274                         if (view_pan_init(C, op)) {
1275                                 v2dViewPanData *vpd;
1276                                 float pan_vec[3];
1277
1278                                 WM_event_ndof_pan_get(ndof, pan_vec, false);
1279
1280                                 pan_vec[0] *= speed;
1281                                 pan_vec[1] *= speed;
1282
1283                                 vpd = op->customdata;
1284
1285                                 view_pan_apply_ex(C, vpd, pan_vec[0], pan_vec[1]);
1286
1287                                 view_pan_exit(op);
1288                         }
1289                 }
1290
1291                 if (has_zoom) {
1292                         if (view_zoomdrag_init(C, op)) {
1293                                 v2dViewZoomData *vzd;
1294                                 float zoom_factor = zoom_sensitivity * ndof->dt * -ndof->tvec[2];
1295
1296                                 bool do_zoom_xy[2];
1297
1298                                 if (U.ndof_flag & NDOF_ZOOM_INVERT)
1299                                         zoom_factor = -zoom_factor;
1300
1301                                 view_zoom_axis_lock_defaults(C, do_zoom_xy);
1302
1303                                 vzd = op->customdata;
1304
1305                                 view_zoomstep_apply_ex(C, vzd, false,
1306                                                        do_zoom_xy[0] ? zoom_factor : 0.0f,
1307                                                        do_zoom_xy[1] ? zoom_factor : 0.0f);
1308
1309                                 view_zoomstep_exit(op);
1310                         }
1311                 }
1312
1313                 return OPERATOR_FINISHED;
1314         }
1315 }
1316
1317 static void VIEW2D_OT_ndof(wmOperatorType *ot)
1318 {
1319         /* identifiers */
1320         ot->name = "NDOF Pan/Zoom";
1321         ot->idname = "VIEW2D_OT_ndof";
1322         ot->description = "Use a 3D mouse device to pan/zoom the view";
1323
1324         /* api callbacks */
1325         ot->invoke = view2d_ndof_invoke;
1326         ot->poll = view2d_poll;
1327
1328         /* flags */
1329         ot->flag = OPTYPE_LOCK_BYPASS;
1330 }
1331
1332 /* ********************************************************* */
1333 /* SMOOTH VIEW */
1334
1335 struct SmoothView2DStore {
1336         rctf orig_cur, new_cur;
1337
1338         double time_allowed;
1339 };
1340
1341 /**
1342  * function to get a factor out of a rectangle
1343  *
1344  * note: this doesn't always work as well as it might because the target size
1345  *       may not be reached because of clamping the desired rect, we _could_
1346  *       attempt to clamp the rect before working out the zoom factor but its
1347  *       not really worthwhile for the few cases this happens.
1348  */
1349 static float smooth_view_rect_to_fac(const rctf *rect_a, const rctf *rect_b)
1350 {
1351         float size_a[2] = {BLI_rctf_size_x(rect_a),
1352                            BLI_rctf_size_y(rect_a)};
1353         float size_b[2] = {BLI_rctf_size_x(rect_b),
1354                            BLI_rctf_size_y(rect_b)};
1355         float cent_a[2] = {BLI_rctf_cent_x(rect_a),
1356                            BLI_rctf_cent_y(rect_a)};
1357         float cent_b[2] = {BLI_rctf_cent_x(rect_b),
1358                            BLI_rctf_cent_y(rect_b)};
1359
1360         float fac_max = 0.0f;
1361         float tfac;
1362
1363         int i;
1364
1365         for (i = 0; i < 2; i++) {
1366                 /* axis translation normalized to scale */
1367                 tfac = fabsf(cent_a[i] - cent_b[i]) / min_ff(size_a[i], size_b[i]);
1368                 fac_max = max_ff(fac_max, tfac);
1369                 if (fac_max >= 1.0f) break;
1370
1371                 /* axis scale difference, x2 so doubling or half gives 1.0f */
1372                 tfac = (1.0f - (min_ff(size_a[i], size_b[i]) / max_ff(size_a[i], size_b[i]))) * 2.0f;
1373                 fac_max = max_ff(fac_max, tfac);
1374                 if (fac_max >= 1.0f) break;
1375         }
1376         return min_ff(fac_max, 1.0f);
1377 }
1378
1379 /* will start timer if appropriate */
1380 /* the arguments are the desired situation */
1381 void UI_view2d_smooth_view(bContext *C, ARegion *ar,
1382                            const rctf *cur, const int smooth_viewtx)
1383 {
1384         wmWindowManager *wm = CTX_wm_manager(C);
1385         wmWindow *win = CTX_wm_window(C);
1386
1387         View2D *v2d = &ar->v2d;
1388         struct SmoothView2DStore sms = {{0}};
1389         bool ok = false;
1390         float fac = 1.0f;
1391
1392         /* initialize sms */
1393         sms.new_cur = v2d->cur;
1394
1395         /* store the options we want to end with */
1396         if (cur) sms.new_cur = *cur;
1397
1398         if (cur) {
1399                 fac = smooth_view_rect_to_fac(&v2d->cur, cur);
1400         }
1401
1402         if (smooth_viewtx && fac > FLT_EPSILON) {
1403                 bool changed = false;
1404
1405                 if (BLI_rctf_compare(&sms.new_cur, &v2d->cur, FLT_EPSILON) == false)
1406                         changed = true;
1407
1408                 /* The new view is different from the old one
1409                  * so animate the view */
1410                 if (changed) {
1411                         sms.orig_cur = v2d->cur;
1412
1413                         sms.time_allowed = (double)smooth_viewtx / 1000.0;
1414
1415                         /* scale the time allowed the change in view */
1416                         sms.time_allowed *= (double)fac;
1417
1418                         /* keep track of running timer! */
1419                         if (v2d->sms == NULL)
1420                                 v2d->sms = MEM_mallocN(sizeof(struct SmoothView2DStore), "smoothview v2d");
1421                         *v2d->sms = sms;
1422                         if (v2d->smooth_timer)
1423                                 WM_event_remove_timer(wm, win, v2d->smooth_timer);
1424                         /* TIMER1 is hardcoded in keymap */
1425                         v2d->smooth_timer = WM_event_add_timer(wm, win, TIMER1, 1.0 / 100.0); /* max 30 frs/sec */
1426
1427                         ok = true;
1428                 }
1429         }
1430
1431         /* if we get here nothing happens */
1432         if (ok == false) {
1433                 v2d->cur = sms.new_cur;
1434
1435                 UI_view2d_curRect_validate(v2d);
1436                 ED_region_tag_redraw(ar);
1437                 UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
1438         }
1439 }
1440
1441 /* only meant for timer usage */
1442 static int view2d_smoothview_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
1443 {
1444         ARegion *ar = CTX_wm_region(C);
1445         View2D *v2d = &ar->v2d;
1446         struct SmoothView2DStore *sms = v2d->sms;
1447         float step;
1448
1449         /* escape if not our timer */
1450         if (v2d->smooth_timer == NULL || v2d->smooth_timer != event->customdata)
1451                 return OPERATOR_PASS_THROUGH;
1452
1453         if (sms->time_allowed != 0.0)
1454                 step = (float)((v2d->smooth_timer->duration) / sms->time_allowed);
1455         else
1456                 step = 1.0f;
1457
1458         /* end timer */
1459         if (step >= 1.0f) {
1460                 v2d->cur = sms->new_cur;
1461
1462                 MEM_freeN(v2d->sms);
1463                 v2d->sms = NULL;
1464
1465                 WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), v2d->smooth_timer);
1466                 v2d->smooth_timer = NULL;
1467         }
1468         else {
1469                 /* ease in/out */
1470                 step = (3.0f * step * step - 2.0f * step * step * step);
1471
1472                 BLI_rctf_interp(&v2d->cur, &sms->orig_cur, &sms->new_cur, step);
1473         }
1474
1475         UI_view2d_curRect_validate(v2d);
1476         UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
1477         ED_region_tag_redraw(ar);
1478
1479         if (v2d->sms == NULL) {
1480                 UI_view2d_zoom_cache_reset();
1481         }
1482
1483         return OPERATOR_FINISHED;
1484 }
1485
1486 static void VIEW2D_OT_smoothview(wmOperatorType *ot)
1487 {
1488         /* identifiers */
1489         ot->name = "Smooth View 2D";
1490         ot->description = "";
1491         ot->idname = "VIEW2D_OT_smoothview";
1492
1493         /* api callbacks */
1494         ot->invoke = view2d_smoothview_invoke;
1495         ot->poll = view2d_poll;
1496
1497         /* flags */
1498         ot->flag = OPTYPE_INTERNAL;
1499
1500         /* rna */
1501         WM_operator_properties_gesture_border(ot, false);
1502 }
1503
1504 /* ********************************************************* */
1505 /* SCROLLERS */
1506
1507 /*  Scrollers should behave in the following ways, when clicked on with LMB (and dragged):
1508  *              1) 'Handles' on end of 'bubble' - when the axis that the scroller represents is zoomable, 
1509  *                      enlarge 'cur' rect on the relevant side 
1510  *              2) 'Bubble'/'bar' - just drag, and bar should move with mouse (view pans opposite)
1511  *
1512  *      In order to make sure this works, each operator must define the following RNA-Operator Props:
1513  *              deltax, deltay  - define how much to move view by (relative to zoom-correction factor)
1514  */
1515
1516 /* customdata for scroller-invoke data */
1517 typedef struct v2dScrollerMove {
1518         View2D *v2d;            /* View2D data that this operation affects */
1519         ARegion *ar;            /* region that the scroller is in */
1520         
1521         short scroller;         /* scroller that mouse is in ('h' or 'v') */
1522         short zone; /* -1 is min zoomer, 0 is bar, 1 is max zoomer */             // XXX find some way to provide visual feedback of this (active color?)
1523         
1524         float fac;              /* view adjustment factor, based on size of region */
1525         float delta;            /* amount moved by mouse on axis of interest */
1526         
1527         float scrollbarwidth;   /* width of the scrollbar itself, used for page up/down clicks */
1528         int scrollbar_orig;      /* initial location of scrollbar x/y, mouse relative */
1529         
1530         int lastx, lasty;       /* previous mouse coordinates (in screen coordinates) for determining movement */
1531 } v2dScrollerMove;
1532
1533
1534 /* View2DScrollers is typedef'd in UI_view2d.h 
1535  * This is a CUT DOWN VERSION of the 'real' version, which is defined in view2d.c, as we only need focus bubble info
1536  * WARNING: the start of this struct must not change, so that it stays in sync with the 'real' version
1537  *         For now, we don't need to have a separate (internal) header for structs like this...
1538  */
1539 struct View2DScrollers {        
1540         /* focus bubbles */
1541         int vert_min, vert_max; /* vertical scrollbar */
1542         int hor_min, hor_max;   /* horizontal scrollbar */
1543 };
1544
1545 /* quick enum for vsm->zone (scroller handles) */
1546 enum {
1547         SCROLLHANDLE_MIN = -1,
1548         SCROLLHANDLE_BAR,
1549         SCROLLHANDLE_MAX,
1550         SCROLLHANDLE_MIN_OUTSIDE,
1551         SCROLLHANDLE_MAX_OUTSIDE
1552 } /*eV2DScrollerHandle_Zone*/;
1553
1554 /* ------------------------ */
1555
1556 /* check if mouse is within scroller handle 
1557  *      - mouse                 =   relevant mouse coordinate in region space
1558  *      - sc_min, sc_max        =   extents of scroller 'groove' (potential available space for scroller)
1559  *      - sh_min, sh_max        =   positions of scrollbar handles
1560  */
1561 static short mouse_in_scroller_handle(int mouse, int sc_min, int sc_max, int sh_min, int sh_max)
1562 {
1563         short in_min, in_max, in_bar, out_min, out_max, in_view = 1;
1564         
1565         /* firstly, check if 
1566          *      - 'bubble' fills entire scroller 
1567          *      - 'bubble' completely out of view on either side 
1568          */
1569         if ((sh_min <= sc_min) && (sh_max >= sc_max)) in_view = 0;
1570         if (sh_min == sh_max) {
1571                 if (sh_min <= sc_min) in_view = 0;
1572                 if (sh_max >= sc_max) in_view = 0;
1573         }
1574         else {
1575                 if (sh_max <= sc_min) in_view = 0;
1576                 if (sh_min >= sc_max) in_view = 0;
1577         }
1578         
1579         
1580         if (in_view == 0) {
1581                 return SCROLLHANDLE_BAR;
1582         }
1583         
1584         /* check if mouse is in or past either handle */
1585         /* TODO: check if these extents are still valid or not */
1586         in_max = ( (mouse >= (sh_max - V2D_SCROLLER_HANDLE_SIZE)) && (mouse <= (sh_max + V2D_SCROLLER_HANDLE_SIZE)) );
1587         in_min = ( (mouse <= (sh_min + V2D_SCROLLER_HANDLE_SIZE)) && (mouse >= (sh_min - V2D_SCROLLER_HANDLE_SIZE)) );
1588         in_bar = ( (mouse < (sh_max - V2D_SCROLLER_HANDLE_SIZE)) && (mouse > (sh_min + V2D_SCROLLER_HANDLE_SIZE)) );
1589         out_min = mouse < (sh_min - V2D_SCROLLER_HANDLE_SIZE);
1590         out_max = mouse > (sh_max + V2D_SCROLLER_HANDLE_SIZE);
1591         
1592         if (in_bar)
1593                 return SCROLLHANDLE_BAR;
1594         else if (in_max)
1595                 return SCROLLHANDLE_MAX;
1596         else if (in_min)
1597                 return SCROLLHANDLE_MIN;
1598         else if (out_min)
1599                 return SCROLLHANDLE_MIN_OUTSIDE;
1600         else if (out_max)
1601                 return SCROLLHANDLE_MAX_OUTSIDE;
1602         
1603         /* unlikely to happen, though we just cover it in case */
1604         return SCROLLHANDLE_BAR;
1605
1606
1607 /* initialize customdata for scroller manipulation operator */
1608 static void scroller_activate_init(bContext *C, wmOperator *op, const wmEvent *event, short in_scroller)
1609 {
1610         v2dScrollerMove *vsm;
1611         View2DScrollers *scrollers;
1612         ARegion *ar = CTX_wm_region(C);
1613         View2D *v2d = &ar->v2d;
1614         rctf tot_cur_union;
1615         float mask_size;
1616         
1617         /* set custom-data for operator */
1618         vsm = MEM_callocN(sizeof(v2dScrollerMove), "v2dScrollerMove");
1619         op->customdata = vsm;
1620         
1621         /* set general data */
1622         vsm->v2d = v2d;
1623         vsm->ar = ar;
1624         vsm->scroller = in_scroller;
1625
1626         /* store mouse-coordinates, and convert mouse/screen coordinates to region coordinates */
1627         vsm->lastx = event->x;
1628         vsm->lasty = event->y;
1629         /* 'zone' depends on where mouse is relative to bubble 
1630          *      - zooming must be allowed on this axis, otherwise, default to pan
1631          */
1632         scrollers = UI_view2d_scrollers_calc(C, v2d, V2D_ARG_DUMMY, V2D_ARG_DUMMY, V2D_ARG_DUMMY, V2D_ARG_DUMMY);
1633
1634         /* use a union of 'cur' & 'tot' incase the current view is far outside 'tot'.
1635          * In this cases moving the scroll bars has far too little effect and the view can get stuck [#31476] */
1636         tot_cur_union = v2d->tot;
1637         BLI_rctf_union(&tot_cur_union, &v2d->cur);
1638
1639         if (in_scroller == 'h') {
1640                 /* horizontal scroller - calculate adjustment factor first */
1641                 mask_size = (float)BLI_rcti_size_x(&v2d->hor);
1642                 vsm->fac = BLI_rctf_size_x(&tot_cur_union) / mask_size;
1643                 
1644                 /* get 'zone' (i.e. which part of scroller is activated) */
1645                 vsm->zone = mouse_in_scroller_handle(event->mval[0],
1646                                                      v2d->hor.xmin, v2d->hor.xmax,
1647                                                      scrollers->hor_min, scrollers->hor_max);
1648                 
1649                 if ((v2d->keepzoom & V2D_LOCKZOOM_X) && ELEM(vsm->zone, SCROLLHANDLE_MIN, SCROLLHANDLE_MAX)) {
1650                         /* default to scroll, as handles not usable */
1651                         vsm->zone = SCROLLHANDLE_BAR;
1652                 }
1653
1654                 vsm->scrollbarwidth = scrollers->hor_max - scrollers->hor_min;
1655                 vsm->scrollbar_orig = ((scrollers->hor_max + scrollers->hor_min) / 2) + ar->winrct.xmin;
1656         }
1657         else {
1658                 /* vertical scroller - calculate adjustment factor first */
1659                 mask_size = (float)BLI_rcti_size_y(&v2d->vert);
1660                 vsm->fac = BLI_rctf_size_y(&tot_cur_union) / mask_size;
1661                 
1662                 /* get 'zone' (i.e. which part of scroller is activated) */
1663                 vsm->zone = mouse_in_scroller_handle(event->mval[1],
1664                                                      v2d->vert.ymin, v2d->vert.ymax,
1665                                                      scrollers->vert_min, scrollers->vert_max);
1666                         
1667                 if ((v2d->keepzoom & V2D_LOCKZOOM_Y) && ELEM(vsm->zone, SCROLLHANDLE_MIN, SCROLLHANDLE_MAX)) {
1668                         /* default to scroll, as handles not usable */
1669                         vsm->zone = SCROLLHANDLE_BAR;
1670                 }
1671                 
1672                 vsm->scrollbarwidth = scrollers->vert_max - scrollers->vert_min;
1673                 vsm->scrollbar_orig = ((scrollers->vert_max + scrollers->vert_min) / 2) + ar->winrct.ymin;
1674         }
1675         
1676         UI_view2d_scrollers_free(scrollers);
1677         ED_region_tag_redraw(ar);
1678 }
1679
1680 /* cleanup temp customdata  */
1681 static void scroller_activate_exit(bContext *C, wmOperator *op)
1682 {
1683         if (op->customdata) {
1684                 v2dScrollerMove *vsm = op->customdata;
1685
1686                 vsm->v2d->scroll_ui &= ~(V2D_SCROLL_H_ACTIVE | V2D_SCROLL_V_ACTIVE);
1687                 
1688                 MEM_freeN(op->customdata);
1689                 op->customdata = NULL;
1690                 
1691                 ED_region_tag_redraw(CTX_wm_region(C));
1692         }
1693 }
1694
1695 static void scroller_activate_cancel(bContext *C, wmOperator *op)
1696 {
1697         scroller_activate_exit(C, op);
1698 }
1699
1700 /* apply transform to view (i.e. adjust 'cur' rect) */
1701 static void scroller_activate_apply(bContext *C, wmOperator *op)
1702 {
1703         v2dScrollerMove *vsm = op->customdata;
1704         View2D *v2d = vsm->v2d;
1705         float temp;
1706         
1707         /* calculate amount to move view by */
1708         temp = vsm->fac * vsm->delta;
1709         
1710         /* type of movement */
1711         switch (vsm->zone) {
1712                 case SCROLLHANDLE_MIN:
1713                         /* only expand view on axis if zoom is allowed */
1714                         if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X))
1715                                 v2d->cur.xmin -= temp;
1716                         if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y))
1717                                 v2d->cur.ymin -= temp;
1718                         break;
1719                         
1720                 case SCROLLHANDLE_MAX:
1721                         
1722                         /* only expand view on axis if zoom is allowed */
1723                         if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X))
1724                                 v2d->cur.xmax += temp;
1725                         if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y))
1726                                 v2d->cur.ymax += temp;
1727                         break;
1728                         
1729                 case SCROLLHANDLE_MIN_OUTSIDE:
1730                 case SCROLLHANDLE_MAX_OUTSIDE:
1731                 case SCROLLHANDLE_BAR:
1732                 default:
1733                         /* only move view on an axis if panning is allowed */
1734                         if ((vsm->scroller == 'h') && !(v2d->keepofs & V2D_LOCKOFS_X)) {
1735                                 v2d->cur.xmin += temp;
1736                                 v2d->cur.xmax += temp;
1737                         }
1738                         if ((vsm->scroller == 'v') && !(v2d->keepofs & V2D_LOCKOFS_Y)) {
1739                                 v2d->cur.ymin += temp;
1740                                 v2d->cur.ymax += temp;
1741                         }
1742                         break;
1743                         
1744         }
1745         
1746         /* validate that view is in valid configuration after this operation */
1747         UI_view2d_curRect_validate(v2d);
1748         
1749         /* request updates to be done... */
1750         ED_region_tag_redraw(vsm->ar);
1751         UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
1752 }
1753
1754 /* handle user input for scrollers - calculations of mouse-movement need to be done here, not in the apply callback! */
1755 static int scroller_activate_modal(bContext *C, wmOperator *op, const wmEvent *event)
1756 {
1757         v2dScrollerMove *vsm = op->customdata;
1758         
1759         /* execute the events */
1760         switch (event->type) {
1761                 case MOUSEMOVE:
1762                 {
1763                         /* calculate new delta transform, then store mouse-coordinates for next-time */
1764                         if (ELEM(vsm->zone, SCROLLHANDLE_BAR, SCROLLHANDLE_MAX)) {
1765                                 /* if using bar (i.e. 'panning') or 'max' zoom widget */
1766                                 switch (vsm->scroller) {
1767                                         case 'h': /* horizontal scroller - so only horizontal movement ('cur' moves opposite to mouse) */
1768                                                 vsm->delta = (float)(event->x - vsm->lastx);
1769                                                 break;
1770                                         case 'v': /* vertical scroller - so only vertical movement ('cur' moves opposite to mouse) */
1771                                                 vsm->delta = (float)(event->y - vsm->lasty);
1772                                                 break;
1773                                 }
1774                         }
1775                         else if (vsm->zone == SCROLLHANDLE_MIN) {
1776                                 /* using 'min' zoom widget */
1777                                 switch (vsm->scroller) {
1778                                         case 'h': /* horizontal scroller - so only horizontal movement ('cur' moves with mouse) */
1779                                                 vsm->delta = (float)(vsm->lastx - event->x);
1780                                                 break;
1781                                         case 'v': /* vertical scroller - so only vertical movement ('cur' moves with to mouse) */
1782                                                 vsm->delta = (float)(vsm->lasty - event->y);
1783                                                 break;
1784                                 }
1785                         }
1786                         
1787                         /* store previous coordinates */
1788                         vsm->lastx = event->x;
1789                         vsm->lasty = event->y;
1790                         
1791                         scroller_activate_apply(C, op);
1792                         break;
1793                 }
1794                 case LEFTMOUSE:
1795                 case MIDDLEMOUSE:
1796                         if (event->val == KM_RELEASE) {
1797                                 /* single-click was in empty space outside bubble, so scroll by 1 'page' */
1798                                 if (ELEM(vsm->zone, SCROLLHANDLE_MIN_OUTSIDE, SCROLLHANDLE_MAX_OUTSIDE)) {
1799                                         if (vsm->zone == SCROLLHANDLE_MIN_OUTSIDE)
1800                                                 vsm->delta = -vsm->scrollbarwidth * 0.8f;
1801                                         else if (vsm->zone == SCROLLHANDLE_MAX_OUTSIDE)
1802                                                 vsm->delta = vsm->scrollbarwidth * 0.8f;
1803                                         
1804                                         scroller_activate_apply(C, op);
1805                                         scroller_activate_exit(C, op);
1806                                         return OPERATOR_FINISHED;
1807                                 }
1808                                 
1809                                 /* otherwise, end the drag action  */
1810                                 if (vsm->lastx || vsm->lasty) {
1811                                         scroller_activate_exit(C, op);
1812                                         return OPERATOR_FINISHED;
1813                                 }
1814                         }
1815                         break;
1816
1817         }
1818
1819         return OPERATOR_RUNNING_MODAL;
1820 }
1821
1822
1823 /* a click (or click drag in progress) should have occurred, so check if it happened in scrollbar */
1824 static int scroller_activate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1825 {
1826         ARegion *ar = CTX_wm_region(C);
1827         View2D *v2d = &ar->v2d;
1828         short in_scroller = 0;
1829                 
1830         /* check if mouse in scrollbars, if they're enabled */
1831         in_scroller = UI_view2d_mouse_in_scrollers(C, v2d, event->x, event->y);
1832         
1833         /* if in a scroller, init customdata then set modal handler which will catch mousedown to start doing useful stuff */
1834         if (in_scroller) {
1835                 v2dScrollerMove *vsm;
1836                 
1837                 /* initialize customdata */
1838                 scroller_activate_init(C, op, event, in_scroller);
1839                 vsm = (v2dScrollerMove *)op->customdata;
1840                 
1841                 /* support for quick jump to location - gtk and qt do this on linux */
1842                 if (event->type == MIDDLEMOUSE) {
1843                         switch (vsm->scroller) {
1844                                 case 'h': /* horizontal scroller - so only horizontal movement ('cur' moves opposite to mouse) */
1845                                         vsm->delta = (float)(event->x - vsm->scrollbar_orig);
1846                                         break;
1847                                 case 'v': /* vertical scroller - so only vertical movement ('cur' moves opposite to mouse) */
1848                                         vsm->delta = (float)(event->y - vsm->scrollbar_orig);
1849                                         break;
1850                         }
1851                         scroller_activate_apply(C, op);
1852
1853                         vsm->zone = SCROLLHANDLE_BAR;
1854                 }
1855
1856                 /* check if zoom zones are inappropriate (i.e. zoom widgets not shown), so cannot continue
1857                  * NOTE: see view2d.c for latest conditions, and keep this in sync with that
1858                  */
1859                 if (ELEM(vsm->zone, SCROLLHANDLE_MIN, SCROLLHANDLE_MAX)) {
1860                         if (((vsm->scroller == 'h') && (v2d->scroll & V2D_SCROLL_SCALE_HORIZONTAL) == 0) ||
1861                             ((vsm->scroller == 'v') && (v2d->scroll & V2D_SCROLL_SCALE_VERTICAL) == 0))
1862                         {
1863                                 /* switch to bar (i.e. no scaling gets handled) */
1864                                 vsm->zone = SCROLLHANDLE_BAR;
1865                         }
1866                 }
1867                 
1868                 /* check if zone is inappropriate (i.e. 'bar' but panning is banned), so cannot continue */
1869                 if (vsm->zone == SCROLLHANDLE_BAR) {
1870                         if (((vsm->scroller == 'h') && (v2d->keepofs & V2D_LOCKOFS_X)) ||
1871                             ((vsm->scroller == 'v') && (v2d->keepofs & V2D_LOCKOFS_Y)))
1872                         {
1873                                 /* free customdata initialized */
1874                                 scroller_activate_exit(C, op);
1875                                 
1876                                 /* can't catch this event for ourselves, so let it go to someone else? */
1877                                 return OPERATOR_PASS_THROUGH;
1878                         }
1879                 }
1880                 
1881                 /* zone is also inappropriate if scroller is not visible... */
1882                 if (((vsm->scroller == 'h') && (v2d->scroll & (V2D_SCROLL_HORIZONTAL_FULLR))) ||
1883                     ((vsm->scroller == 'v') && (v2d->scroll & (V2D_SCROLL_VERTICAL_FULLR))) )
1884                 {
1885                         /* free customdata initialized */
1886                         scroller_activate_exit(C, op);
1887                                 
1888                         /* can't catch this event for ourselves, so let it go to someone else? */
1889                         /* XXX note: if handlers use mask rect to clip input, input will fail for this case */
1890                         return OPERATOR_PASS_THROUGH;
1891                 }
1892                 
1893                 /* activate the scroller */
1894                 if (vsm->scroller == 'h')
1895                         v2d->scroll_ui |= V2D_SCROLL_H_ACTIVE;
1896                 else
1897                         v2d->scroll_ui |= V2D_SCROLL_V_ACTIVE;
1898                 
1899                 /* still ok, so can add */
1900                 WM_event_add_modal_handler(C, op);
1901                 return OPERATOR_RUNNING_MODAL;
1902         }
1903         else {
1904                 /* not in scroller, so nothing happened... (pass through let's something else catch event) */
1905                 return OPERATOR_PASS_THROUGH;
1906         }
1907 }
1908
1909 /* LMB-Drag in Scrollers - not repeatable operator! */
1910 static void VIEW2D_OT_scroller_activate(wmOperatorType *ot)
1911 {
1912         /* identifiers */
1913         ot->name = "Scroller Activate";
1914         ot->description = "Scroll view by mouse click and drag";
1915         ot->idname = "VIEW2D_OT_scroller_activate";
1916
1917         /* flags */
1918         ot->flag = OPTYPE_BLOCKING;
1919         
1920         /* api callbacks */
1921         ot->invoke = scroller_activate_invoke;
1922         ot->modal = scroller_activate_modal;
1923         ot->cancel = scroller_activate_cancel;
1924
1925         ot->poll = view2d_poll;
1926 }
1927
1928 /* ********************************************************* */
1929 /* RESET */
1930
1931 static int reset_exec(bContext *C, wmOperator *UNUSED(op))
1932 {
1933         uiStyle *style = UI_GetStyle();
1934         ARegion *ar = CTX_wm_region(C);
1935         View2D *v2d = &ar->v2d;
1936         int winx, winy;
1937
1938         /* zoom 1.0 */
1939         winx = (float)(BLI_rcti_size_x(&v2d->mask) + 1);
1940         winy = (float)(BLI_rcti_size_y(&v2d->mask) + 1);
1941
1942         v2d->cur.xmax = v2d->cur.xmin + winx;
1943         v2d->cur.ymax = v2d->cur.ymin + winy;
1944         
1945         /* align */
1946         if (v2d->align) {
1947                 /* posx and negx flags are mutually exclusive, so watch out */
1948                 if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) {
1949                         v2d->cur.xmax = 0.0f;
1950                         v2d->cur.xmin = -winx * style->panelzoom;
1951                 }
1952                 else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) {
1953                         v2d->cur.xmax = winx * style->panelzoom;
1954                         v2d->cur.xmin = 0.0f;
1955                 }
1956
1957                 /* - posx and negx flags are mutually exclusive, so watch out */
1958                 if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) {
1959                         v2d->cur.ymax = 0.0f;
1960                         v2d->cur.ymin = -winy * style->panelzoom;
1961                 }
1962                 else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) {
1963                         v2d->cur.ymax = winy * style->panelzoom;
1964                         v2d->cur.ymin = 0.0f;
1965                 }
1966         }
1967
1968         /* validate that view is in valid configuration after this operation */
1969         UI_view2d_curRect_validate(v2d);
1970         
1971         /* request updates to be done... */
1972         ED_region_tag_redraw(ar);
1973         UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
1974         
1975         UI_view2d_zoom_cache_reset();
1976
1977         return OPERATOR_FINISHED;
1978 }
1979
1980 static void VIEW2D_OT_reset(wmOperatorType *ot)
1981 {
1982         /* identifiers */
1983         ot->name = "Reset View";
1984         ot->description = "Reset the view";
1985         ot->idname = "VIEW2D_OT_reset";
1986         
1987         /* api callbacks */
1988         ot->exec = reset_exec;
1989         ot->poll = view2d_poll;
1990 }
1991  
1992 /* ********************************************************* */
1993 /* Registration */
1994
1995 void UI_view2d_operatortypes(void)
1996 {
1997         WM_operatortype_append(VIEW2D_OT_pan);
1998         
1999         WM_operatortype_append(VIEW2D_OT_scroll_left);
2000         WM_operatortype_append(VIEW2D_OT_scroll_right);
2001         WM_operatortype_append(VIEW2D_OT_scroll_up);
2002         WM_operatortype_append(VIEW2D_OT_scroll_down);
2003         
2004         WM_operatortype_append(VIEW2D_OT_zoom_in);
2005         WM_operatortype_append(VIEW2D_OT_zoom_out);
2006         
2007         WM_operatortype_append(VIEW2D_OT_zoom);
2008         WM_operatortype_append(VIEW2D_OT_zoom_border);
2009
2010         WM_operatortype_append(VIEW2D_OT_ndof);
2011
2012         WM_operatortype_append(VIEW2D_OT_smoothview);
2013         
2014         WM_operatortype_append(VIEW2D_OT_scroller_activate);
2015
2016         WM_operatortype_append(VIEW2D_OT_reset);
2017 }
2018
2019 void UI_view2d_keymap(wmKeyConfig *keyconf)
2020 {
2021         wmKeyMap *keymap = WM_keymap_find(keyconf, "View2D", 0, 0);
2022         wmKeyMapItem *kmi;
2023
2024         /* scrollers */
2025         WM_keymap_add_item(keymap, "VIEW2D_OT_scroller_activate", LEFTMOUSE, KM_PRESS, 0, 0);
2026         WM_keymap_add_item(keymap, "VIEW2D_OT_scroller_activate", MIDDLEMOUSE, KM_PRESS, 0, 0);
2027
2028         /* pan/scroll */
2029         WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MIDDLEMOUSE, KM_PRESS, 0, 0);
2030         WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MIDDLEMOUSE, KM_PRESS, KM_SHIFT, 0);
2031         
2032         WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MOUSEPAN, 0, 0, 0);
2033         
2034         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_right", WHEELDOWNMOUSE, KM_PRESS, KM_CTRL, 0);
2035         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_left", WHEELUPMOUSE, KM_PRESS, KM_CTRL, 0);
2036         
2037         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_down", WHEELDOWNMOUSE, KM_PRESS, KM_SHIFT, 0);
2038         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_up", WHEELUPMOUSE, KM_PRESS, KM_SHIFT, 0);
2039         
2040         WM_keymap_add_item(keymap, "VIEW2D_OT_ndof", NDOF_MOTION, 0, 0, 0);
2041
2042         /* zoom - single step */
2043         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_out", WHEELOUTMOUSE, KM_PRESS, 0, 0);
2044         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_in", WHEELINMOUSE, KM_PRESS, 0, 0);
2045         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_out", PADMINUS, KM_PRESS, 0, 0);
2046         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_in", PADPLUSKEY, KM_PRESS, 0, 0);
2047         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MOUSEPAN, 0, KM_CTRL, 0);
2048         
2049         WM_keymap_verify_item(keymap, "VIEW2D_OT_smoothview", TIMER1, KM_ANY, KM_ANY, 0);
2050
2051         /* scroll up/down - no modifiers, only when zoom fails */
2052         /* these may fail if zoom is disallowed, in which case they should pass on event */
2053         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_down", WHEELDOWNMOUSE, KM_PRESS, 0, 0);
2054         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_up", WHEELUPMOUSE, KM_PRESS, 0, 0);
2055         /* these may be necessary if vertical scroll is disallowed */
2056         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_right", WHEELDOWNMOUSE, KM_PRESS, 0, 0);
2057         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_left", WHEELUPMOUSE, KM_PRESS, 0, 0);
2058         
2059         /* alternatives for page up/down to scroll */
2060 #if 0 // XXX disabled, since this causes conflicts with hotkeys in animation editors
2061         /* scroll up/down may fall through to left/right */
2062         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_down", PAGEDOWNKEY, KM_PRESS, 0, 0);
2063         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_up", PAGEUPKEY, KM_PRESS, 0, 0);
2064         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_right", PAGEDOWNKEY, KM_PRESS, 0, 0);
2065         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_left", PAGEUPKEY, KM_PRESS, 0, 0);
2066         /* shift for moving view left/right with page up/down */
2067         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_right", PAGEDOWNKEY, KM_PRESS, KM_SHIFT, 0);
2068         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_left", PAGEUPKEY, KM_PRESS, KM_SHIFT, 0);
2069 #endif
2070         
2071         /* zoom - drag */
2072         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MIDDLEMOUSE, KM_PRESS, KM_CTRL, 0);
2073         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MOUSEZOOM, 0, 0, 0);
2074         
2075         /* borderzoom - drag */
2076         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_border", BKEY, KM_PRESS, KM_SHIFT, 0);
2077
2078         /* Alternative keymap for buttons listview */
2079         keymap = WM_keymap_find(keyconf, "View2D Buttons List", 0, 0);
2080
2081         WM_keymap_add_item(keymap, "VIEW2D_OT_scroller_activate", LEFTMOUSE, KM_PRESS, 0, 0);
2082         WM_keymap_add_item(keymap, "VIEW2D_OT_scroller_activate", MIDDLEMOUSE, KM_PRESS, 0, 0);
2083
2084         WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MIDDLEMOUSE, KM_PRESS, 0, 0);
2085         WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MOUSEPAN, 0, 0, 0);
2086         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_down", WHEELDOWNMOUSE, KM_PRESS, 0, 0);
2087         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_up", WHEELUPMOUSE, KM_PRESS, 0, 0);
2088         
2089         kmi = WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_down", PAGEDOWNKEY, KM_PRESS, 0, 0);
2090         RNA_boolean_set(kmi->ptr, "page", true);
2091         kmi = WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_up", PAGEUPKEY, KM_PRESS, 0, 0);
2092         RNA_boolean_set(kmi->ptr, "page", true);
2093         
2094         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MIDDLEMOUSE, KM_PRESS, KM_CTRL, 0);
2095         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MOUSEZOOM, 0, 0, 0);
2096         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MOUSEPAN, 0, KM_CTRL, 0);
2097         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_out", PADMINUS, KM_PRESS, 0, 0);
2098         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_in", PADPLUSKEY, KM_PRESS, 0, 0);
2099         WM_keymap_add_item(keymap, "VIEW2D_OT_reset", HOMEKEY, KM_PRESS, 0, 0);
2100 }
2101