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