07275d6be2a95bfda90a50932a5fd1acf98cf49d
[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_PREVIEW)
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 0;
616         v2d = &ar->v2d;
617         
618         /* check that 2d-view is zoomable */
619         if ((v2d->keepzoom & V2D_LOCKZOOM_X) && (v2d->keepzoom & V2D_LOCKZOOM_Y))
620                 return 0;
621                 
622         /* view is zoomable */
623         return 1;
624 }
625  
626 /* apply transform to view (i.e. adjust 'cur' rect) */
627 static void view_zoomstep_apply_ex(
628         bContext *C, v2dViewZoomData *vzd, const bool use_mousepos,
629         const float facx, const float facy)
630 {
631         ARegion *ar = CTX_wm_region(C);
632         View2D *v2d = &ar->v2d;
633         const rctf cur_old = v2d->cur;
634         float dx, dy;
635
636         /* calculate amount to move view by, ensuring symmetry so the
637          * old zoom level is restored after zooming back the same amount 
638          */
639         if (facx >= 0.0f) {
640                 dx = BLI_rctf_size_x(&v2d->cur) * facx;
641                 dy = BLI_rctf_size_y(&v2d->cur) * facy;
642         }
643         else {
644                 dx = (BLI_rctf_size_x(&v2d->cur) / (1.0f + 2.0f * facx)) * facx;
645                 dy = (BLI_rctf_size_y(&v2d->cur) / (1.0f + 2.0f * facy)) * facy;
646         }
647
648         /* only resize view on an axis if change is allowed */
649         if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) {
650                 if (v2d->keepofs & V2D_LOCKOFS_X) {
651                         v2d->cur.xmax -= 2 * dx;
652                 }
653                 else if (v2d->keepofs & V2D_KEEPOFS_X) {
654                         if (v2d->align & V2D_ALIGN_NO_POS_X)
655                                 v2d->cur.xmin += 2 * dx;
656                         else
657                                 v2d->cur.xmax -= 2 * dx;
658                 }
659                 else {
660
661                         v2d->cur.xmin += dx;
662                         v2d->cur.xmax -= dx;
663
664                         if (use_mousepos && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) {
665                                 /* get zoom fac the same way as in ui_view2d_curRect_validate_resize - better keep in sync! */
666                                 const float zoomx = (float)(BLI_rcti_size_x(&v2d->mask) + 1) / BLI_rctf_size_x(&v2d->cur);
667
668                                 /* only move view to mouse if zoom fac is inside minzoom/maxzoom */
669                                 if (((v2d->keepzoom & V2D_LIMITZOOM) == 0) ||
670                                     IN_RANGE_INCL(zoomx, v2d->minzoom, v2d->maxzoom))
671                                 {
672                                         float mval_fac = (vzd->mx_2d - cur_old.xmin) / BLI_rctf_size_x(&cur_old);
673                                         float mval_faci = 1.0f - mval_fac;
674                                         float ofs = (mval_fac * dx) - (mval_faci * dx);
675
676                                         v2d->cur.xmin += ofs;
677                                         v2d->cur.xmax += ofs;
678                                 }
679                         }
680                 }
681         }
682         if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) {
683                 if (v2d->keepofs & V2D_LOCKOFS_Y) {
684                         v2d->cur.ymax -= 2 * dy;
685                 }
686                 else if (v2d->keepofs & V2D_KEEPOFS_Y) {
687                         if (v2d->align & V2D_ALIGN_NO_POS_Y)
688                                 v2d->cur.ymin += 2 * dy;
689                         else
690                                 v2d->cur.ymax -= 2 * dy;
691                 }
692                 else {
693
694                         v2d->cur.ymin += dy;
695                         v2d->cur.ymax -= dy;
696
697                         if (use_mousepos && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) {
698                                 /* get zoom fac the same way as in ui_view2d_curRect_validate_resize - better keep in sync! */
699                                 const float zoomy = (float)(BLI_rcti_size_y(&v2d->mask) + 1) / BLI_rctf_size_y(&v2d->cur);
700
701                                 /* only move view to mouse if zoom fac is inside minzoom/maxzoom */
702                                 if (((v2d->keepzoom & V2D_LIMITZOOM) == 0) ||
703                                     IN_RANGE_INCL(zoomy, v2d->minzoom, v2d->maxzoom))
704                                 {
705                                         float mval_fac = (vzd->my_2d - cur_old.ymin) / BLI_rctf_size_y(&cur_old);
706                                         float mval_faci = 1.0f - mval_fac;
707                                         float ofs = (mval_fac * dy) - (mval_faci * dy);
708
709                                         v2d->cur.ymin += ofs;
710                                         v2d->cur.ymax += ofs;
711                                 }
712                         }
713                 }
714         }
715
716         /* validate that view is in valid configuration after this operation */
717         UI_view2d_curRect_validate(v2d);
718
719         /* request updates to be done... */
720         ED_region_tag_redraw(vzd->ar);
721         UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
722 }
723
724 static void view_zoomstep_apply(bContext *C, wmOperator *op)
725 {
726         v2dViewZoomData *vzd = op->customdata;
727         view_zoomstep_apply_ex(C, vzd, true,
728                                RNA_float_get(op->ptr, "zoomfacx"),
729                                RNA_float_get(op->ptr, "zoomfacy"));
730 }
731
732 /* --------------- Individual Operators ------------------- */
733
734 /* cleanup temp customdata  */
735 static void view_zoomstep_exit(wmOperator *op)
736 {
737         UI_view2d_zoom_cache_reset();
738
739         if (op->customdata) {
740                 MEM_freeN(op->customdata);
741                 op->customdata = NULL;
742         }
743 }
744
745 /* this operator only needs this single callback, where it calls the view_zoom_*() methods */
746 static int view_zoomin_exec(bContext *C, wmOperator *op)
747 {
748         bool do_zoom_xy[2];
749
750         /* check that there's an active region, as View2D data resides there */
751         if (!view_zoom_poll(C))
752                 return OPERATOR_PASS_THROUGH;
753         
754
755         view_zoom_axis_lock_defaults(C, do_zoom_xy);
756
757         /* set RNA-Props - zooming in by uniform factor */
758         RNA_float_set(op->ptr, "zoomfacx", do_zoom_xy[0] ? 0.0375f : 0.0f);
759         RNA_float_set(op->ptr, "zoomfacy", do_zoom_xy[1] ? 0.0375f : 0.0f);
760         
761         /* apply movement, then we're done */
762         view_zoomstep_apply(C, op);
763         
764         view_zoomstep_exit(op);
765         
766         return OPERATOR_FINISHED;
767 }
768
769 static int view_zoomin_invoke(bContext *C, wmOperator *op, const wmEvent *event)
770 {
771         v2dViewZoomData *vzd;
772         
773         if (!view_zoomdrag_init(C, op))
774                 return OPERATOR_PASS_THROUGH;
775         
776         vzd = op->customdata;
777         
778         if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
779                 ARegion *ar = CTX_wm_region(C);
780                 
781                 /* store initial mouse position (in view space) */
782                 UI_view2d_region_to_view(&ar->v2d,
783                                          event->mval[0], event->mval[1],
784                                          &vzd->mx_2d, &vzd->my_2d);
785         }
786         
787         return view_zoomin_exec(C, op);
788 }
789
790 static void VIEW2D_OT_zoom_in(wmOperatorType *ot)
791 {
792         PropertyRNA *prop;
793
794         /* identifiers */
795         ot->name = "Zoom In";
796         ot->description = "Zoom in the view";
797         ot->idname = "VIEW2D_OT_zoom_in";
798         
799         /* api callbacks */
800         ot->invoke = view_zoomin_invoke;
801         ot->exec = view_zoomin_exec;  // XXX, needs view_zoomdrag_init called first.
802         ot->poll = view_zoom_poll;
803         
804         /* rna - must keep these in sync with the other operators */
805         prop = RNA_def_float(ot->srna, "zoomfacx", 0, -FLT_MAX, FLT_MAX, "Zoom Factor X", "", -FLT_MAX, FLT_MAX);
806         RNA_def_property_flag(prop, PROP_HIDDEN);
807         prop = RNA_def_float(ot->srna, "zoomfacy", 0, -FLT_MAX, FLT_MAX, "Zoom Factor Y", "", -FLT_MAX, FLT_MAX);
808         RNA_def_property_flag(prop, PROP_HIDDEN);
809 }
810
811 /* this operator only needs this single callback, where it calls the view_zoom_*() methods */
812 static int view_zoomout_exec(bContext *C, wmOperator *op)
813 {
814         bool do_zoom_xy[2];
815
816         /* check that there's an active region, as View2D data resides there */
817         if (!view_zoom_poll(C))
818                 return OPERATOR_PASS_THROUGH;
819         
820         view_zoom_axis_lock_defaults(C, do_zoom_xy);
821
822         /* set RNA-Props - zooming in by uniform factor */
823         RNA_float_set(op->ptr, "zoomfacx", do_zoom_xy[0] ? -0.0375f : 0.0f);
824         RNA_float_set(op->ptr, "zoomfacy", do_zoom_xy[1] ? -0.0375f : 0.0f);
825         
826         /* apply movement, then we're done */
827         view_zoomstep_apply(C, op);
828
829         view_zoomstep_exit(op);
830         
831         return OPERATOR_FINISHED;
832 }
833
834 static int view_zoomout_invoke(bContext *C, wmOperator *op, const wmEvent *event)
835 {
836         v2dViewZoomData *vzd;
837         
838         if (!view_zoomdrag_init(C, op))
839                 return OPERATOR_PASS_THROUGH;
840
841         vzd = op->customdata;
842         
843         if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
844                 ARegion *ar = CTX_wm_region(C);
845                 
846                 /* store initial mouse position (in view space) */
847                 UI_view2d_region_to_view(&ar->v2d, 
848                                          event->mval[0], event->mval[1],
849                                          &vzd->mx_2d, &vzd->my_2d);
850         }
851         
852         return view_zoomout_exec(C, op);
853 }
854
855 static void VIEW2D_OT_zoom_out(wmOperatorType *ot)
856 {
857         PropertyRNA *prop;
858
859         /* identifiers */
860         ot->name = "Zoom Out";
861         ot->description = "Zoom out the view";
862         ot->idname = "VIEW2D_OT_zoom_out";
863         
864         /* api callbacks */
865         ot->invoke = view_zoomout_invoke;
866 //      ot->exec = view_zoomout_exec; // XXX, needs view_zoomdrag_init called first.
867         ot->poll = view_zoom_poll;
868         
869         /* rna - must keep these in sync with the other operators */
870         prop = RNA_def_float(ot->srna, "zoomfacx", 0, -FLT_MAX, FLT_MAX, "Zoom Factor X", "", -FLT_MAX, FLT_MAX);
871         RNA_def_property_flag(prop, PROP_HIDDEN);
872         prop = RNA_def_float(ot->srna, "zoomfacy", 0, -FLT_MAX, FLT_MAX, "Zoom Factor Y", "", -FLT_MAX, FLT_MAX);
873         RNA_def_property_flag(prop, PROP_HIDDEN);
874 }
875
876 /* ********************************************************* */
877 /* DRAG-ZOOM OPERATOR                                                                    */
878
879 /**
880  * MMB Drag - allows non-uniform scaling by dragging mouse
881  *
882  * In order to make sure this works, each operator must define the following RNA-Operator Props:
883  * - `deltax, deltay` - amounts to add to each side of the 'cur' rect
884  */
885
886 /* apply transform to view (i.e. adjust 'cur' rect) */
887 static void view_zoomdrag_apply(bContext *C, wmOperator *op)
888 {
889         v2dViewZoomData *vzd = op->customdata;
890         View2D *v2d = vzd->v2d;
891         float dx, dy;
892         
893         /* get amount to move view by */
894         dx = RNA_float_get(op->ptr, "deltax");
895         dy = RNA_float_get(op->ptr, "deltay");
896
897         if (U.uiflag & USER_ZOOM_INVERT) {
898                 dx *= -1;
899                 dy *= -1;
900         }
901         
902         /* continuous zoom shouldn't move that fast... */
903         if (U.viewzoom == USER_ZOOM_CONT) { // XXX store this setting as RNA prop?
904                 double time = PIL_check_seconds_timer();
905                 float time_step = (float)(time - vzd->timer_lastdraw);
906
907                 dx *= time_step * 0.5f;
908                 dy *= time_step * 0.5f;
909                 
910                 vzd->timer_lastdraw = time;
911         }
912
913         /* only move view on an axis if change is allowed */
914         if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) {
915                 if (v2d->keepofs & V2D_LOCKOFS_X) {
916                         v2d->cur.xmax -= 2 * dx;
917                 }
918                 else {
919                         if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
920                                 float mval_fac = (vzd->mx_2d - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur);
921                                 float mval_faci = 1.0f - mval_fac;
922                                 float ofs = (mval_fac * dx) - (mval_faci * dx);
923                                 
924                                 v2d->cur.xmin += ofs + dx;
925                                 v2d->cur.xmax += ofs - dx;
926                         }
927                         else {
928                                 v2d->cur.xmin += dx;
929                                 v2d->cur.xmax -= dx;
930                         }
931                 }
932         }
933         if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) {
934                 if (v2d->keepofs & V2D_LOCKOFS_Y) {
935                         v2d->cur.ymax -= 2 * dy;
936                 }
937                 else {
938                         if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
939                                 float mval_fac = (vzd->my_2d - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur);
940                                 float mval_faci = 1.0f - mval_fac;
941                                 float ofs = (mval_fac * dy) - (mval_faci * dy);
942                                 
943                                 v2d->cur.ymin += ofs + dy;
944                                 v2d->cur.ymax += ofs - dy;
945                         }
946                         else {
947                                 v2d->cur.ymin += dy;
948                                 v2d->cur.ymax -= dy;
949                         }
950                 }
951         }
952         
953         /* validate that view is in valid configuration after this operation */
954         UI_view2d_curRect_validate(v2d);
955         
956         /* request updates to be done... */
957         ED_region_tag_redraw(vzd->ar);
958         UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
959 }
960
961 /* cleanup temp customdata  */
962 static void view_zoomdrag_exit(bContext *C, wmOperator *op)
963 {
964         UI_view2d_zoom_cache_reset();
965
966         if (op->customdata) {
967                 v2dViewZoomData *vzd = op->customdata;
968                 
969                 if (vzd->timer)
970                         WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), vzd->timer);
971                 
972                 MEM_freeN(op->customdata);
973                 op->customdata = NULL;
974         }
975
976
977 static void view_zoomdrag_cancel(bContext *C, wmOperator *op)
978 {
979         view_zoomdrag_exit(C, op);
980 }
981
982 /* for 'redo' only, with no user input */
983 static int view_zoomdrag_exec(bContext *C, wmOperator *op)
984 {
985         if (!view_zoomdrag_init(C, op))
986                 return OPERATOR_PASS_THROUGH;
987         
988         view_zoomdrag_apply(C, op);
989         view_zoomdrag_exit(C, op);
990         return OPERATOR_FINISHED;
991 }
992
993 /* set up modal operator and relevant settings */
994 static int view_zoomdrag_invoke(bContext *C, wmOperator *op, const wmEvent *event)
995 {
996         wmWindow *window = CTX_wm_window(C);
997         v2dViewZoomData *vzd;
998         View2D *v2d;
999         
1000         /* set up customdata */
1001         if (!view_zoomdrag_init(C, op))
1002                 return OPERATOR_PASS_THROUGH;
1003         
1004         vzd = op->customdata;
1005         v2d = vzd->v2d;
1006         
1007         if (event->type == MOUSEZOOM || event->type == MOUSEPAN) {
1008                 float dx, dy, fac;
1009                 
1010                 vzd->lastx = event->prevx;
1011                 vzd->lasty = event->prevy;
1012                 
1013                 /* As we have only 1D information (magnify value), feed both axes
1014                  * with magnify information that is stored in x axis 
1015                  */
1016                 fac = 0.01f * (event->prevx - event->x);
1017                 dx = fac * BLI_rctf_size_x(&v2d->cur) / 10.0f;
1018                 if (event->type == MOUSEPAN)
1019                         fac = 0.01f * (event->prevy - event->y);
1020                 dy = fac * BLI_rctf_size_y(&v2d->cur) / 10.0f;
1021
1022                 /* support trackpad zoom to always zoom entirely - the v2d code uses portrait or landscape exceptions */
1023                 if (v2d->keepzoom & V2D_KEEPASPECT) {
1024                         if (fabsf(dx) > fabsf(dy))
1025                                 dy = dx;
1026                         else
1027                                 dx = dy;
1028                 }
1029                 RNA_float_set(op->ptr, "deltax", dx);
1030                 RNA_float_set(op->ptr, "deltay", dy);
1031                 
1032                 view_zoomdrag_apply(C, op);
1033                 view_zoomdrag_exit(C, op);
1034                 return OPERATOR_FINISHED;
1035         }
1036         
1037         /* set initial settings */
1038         vzd->lastx = event->x;
1039         vzd->lasty = event->y;
1040         RNA_float_set(op->ptr, "deltax", 0);
1041         RNA_float_set(op->ptr, "deltay", 0);
1042         
1043         /* for modal exit test */
1044         vzd->invoke_event = event->type;
1045         
1046         if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
1047                 ARegion *ar = CTX_wm_region(C);
1048                 
1049                 /* store initial mouse position (in view space) */
1050                 UI_view2d_region_to_view(&ar->v2d, 
1051                                          event->mval[0], event->mval[1],
1052                                          &vzd->mx_2d, &vzd->my_2d);
1053         }
1054
1055         if (v2d->keepofs & V2D_LOCKOFS_X)
1056                 WM_cursor_modal_set(window, BC_NS_SCROLLCURSOR);
1057         else if (v2d->keepofs & V2D_LOCKOFS_Y)
1058                 WM_cursor_modal_set(window, BC_EW_SCROLLCURSOR);
1059         else
1060                 WM_cursor_modal_set(window, BC_NSEW_SCROLLCURSOR);
1061         
1062         /* add temp handler */
1063         WM_event_add_modal_handler(C, op);
1064
1065         if (U.viewzoom == USER_ZOOM_CONT) {
1066                 /* needs a timer to continue redrawing */
1067                 vzd->timer = WM_event_add_timer(CTX_wm_manager(C), window, TIMER, 0.01f);
1068                 vzd->timer_lastdraw = PIL_check_seconds_timer();
1069         }
1070
1071         return OPERATOR_RUNNING_MODAL;
1072 }
1073
1074 /* handle user input - calculations of mouse-movement need to be done here, not in the apply callback! */
1075 static int view_zoomdrag_modal(bContext *C, wmOperator *op, const wmEvent *event)
1076 {
1077         v2dViewZoomData *vzd = op->customdata;
1078         View2D *v2d = vzd->v2d;
1079         
1080         /* execute the events */
1081         if (event->type == TIMER && event->customdata == vzd->timer) {
1082                 view_zoomdrag_apply(C, op);
1083         }
1084         else if (event->type == MOUSEMOVE) {
1085                 float dx, dy;
1086                 
1087                 /* calculate new delta transform, based on zooming mode */
1088                 if (U.viewzoom == USER_ZOOM_SCALE) {
1089                         /* 'scale' zooming */
1090                         float dist;
1091                         
1092                         /* x-axis transform */
1093                         dist = BLI_rcti_size_x(&v2d->mask) / 2.0f;
1094                         dx = 1.0f - (fabsf(vzd->lastx - vzd->ar->winrct.xmin - dist) + 2.0f) / (fabsf(event->mval[0] - dist) + 2.0f);
1095                         dx *= 0.5f * BLI_rctf_size_x(&v2d->cur);
1096                         
1097                         /* y-axis transform */
1098                         dist = BLI_rcti_size_y(&v2d->mask) / 2.0f;
1099                         dy = 1.0f - (fabsf(vzd->lasty - vzd->ar->winrct.ymin - dist) + 2.0f) / (fabsf(event->mval[1] - dist) + 2.0f);
1100                         dy *= 0.5f * BLI_rctf_size_y(&v2d->cur);
1101                 }
1102                 else {
1103                         /* 'continuous' or 'dolly' */
1104                         float fac, zoomfac = 0.01f;
1105                         
1106                         /* some view2d's (graph) don't have min/max zoom, or extreme ones */
1107                         if (v2d->maxzoom > 0.0f)
1108                                 zoomfac = CLAMPIS(0.001f * v2d->maxzoom, 0.001f, 0.01f);
1109                         
1110                         /* x-axis transform */
1111                         fac = zoomfac * (event->x - vzd->lastx);
1112                         dx = fac * BLI_rctf_size_x(&v2d->cur);
1113                         
1114                         /* y-axis transform */
1115                         fac = zoomfac * (event->y - vzd->lasty);
1116                         dy = fac * BLI_rctf_size_y(&v2d->cur);
1117                         
1118                 }
1119                 
1120                 /* support zoom to always zoom entirely - the v2d code uses portrait or landscape exceptions */
1121                 if (v2d->keepzoom & V2D_KEEPASPECT) {
1122                         if (fabsf(dx) > fabsf(dy))
1123                                 dy = dx;
1124                         else
1125                                 dx = dy;
1126                 }
1127                 
1128                 /* set transform amount, and add current deltas to stored total delta (for redo) */
1129                 RNA_float_set(op->ptr, "deltax", dx);
1130                 RNA_float_set(op->ptr, "deltay", dy);
1131
1132                 vzd->dx += dx;
1133                 vzd->dy += dy;
1134                 
1135                 /* store mouse coordinates for next time, if not doing continuous zoom
1136                  *      - continuous zoom only depends on distance of mouse to starting point to determine rate of change
1137                  */
1138                 if (U.viewzoom != USER_ZOOM_CONT) { // XXX store this setting as RNA prop?
1139                         vzd->lastx = event->x;
1140                         vzd->lasty = event->y;
1141                 }
1142                 
1143                 /* apply zooming */
1144                 view_zoomdrag_apply(C, op);
1145         }
1146         else if (event->type == vzd->invoke_event || event->type == ESCKEY) {
1147                 if (event->val == KM_RELEASE) {
1148                         
1149                         /* for redo, store the overall deltas - need to respect zoom-locks here... */
1150                         if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0)
1151                                 RNA_float_set(op->ptr, "deltax", vzd->dx);
1152                         else
1153                                 RNA_float_set(op->ptr, "deltax", 0);
1154                                 
1155                         if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0)
1156                                 RNA_float_set(op->ptr, "deltay", vzd->dy);
1157                         else
1158                                 RNA_float_set(op->ptr, "deltay", 0);
1159                         
1160                         /* free customdata */
1161                         view_zoomdrag_exit(C, op);
1162                         WM_cursor_modal_restore(CTX_wm_window(C));
1163                         
1164                         return OPERATOR_FINISHED;
1165                 }
1166         }
1167
1168         return OPERATOR_RUNNING_MODAL;
1169 }
1170
1171 static void VIEW2D_OT_zoom(wmOperatorType *ot)
1172 {
1173         PropertyRNA *prop;
1174         /* identifiers */
1175         ot->name = "Zoom 2D View";
1176         ot->description = "Zoom in/out the view";
1177         ot->idname = "VIEW2D_OT_zoom";
1178         
1179         /* api callbacks */
1180         ot->exec = view_zoomdrag_exec;
1181         ot->invoke = view_zoomdrag_invoke;
1182         ot->modal = view_zoomdrag_modal;
1183         ot->cancel = view_zoomdrag_cancel;
1184         
1185         ot->poll = view_zoom_poll;
1186         
1187         /* operator is repeatable */
1188         ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR;
1189         
1190         /* rna - must keep these in sync with the other operators */
1191         prop = RNA_def_float(ot->srna, "deltax", 0, -FLT_MAX, FLT_MAX, "Delta X", "", -FLT_MAX, FLT_MAX);
1192         RNA_def_property_flag(prop, PROP_HIDDEN);
1193         prop = RNA_def_float(ot->srna, "deltay", 0, -FLT_MAX, FLT_MAX, "Delta Y", "", -FLT_MAX, FLT_MAX);
1194         RNA_def_property_flag(prop, PROP_HIDDEN);
1195 }
1196
1197 /* ********************************************************* */
1198 /* BORDER-ZOOM */
1199
1200 /**
1201  * The user defines a rect using standard borderselect tools, and we use this rect to
1202  * define the new zoom-level of the view in the following ways:
1203  *
1204  * -# LEFTMOUSE - zoom in to view
1205  * -# RIGHTMOUSE - zoom out of view
1206  *
1207  * Currently, these key mappings are hardcoded, but it shouldn't be too important to
1208  * have custom keymappings for this...
1209  */
1210  
1211 static int view_borderzoom_exec(bContext *C, wmOperator *op)
1212 {
1213         ARegion *ar = CTX_wm_region(C);
1214         View2D *v2d = &ar->v2d;
1215         rctf rect;
1216         rctf cur_new = v2d->cur;
1217         int gesture_mode;
1218         const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
1219         
1220         /* convert coordinates of rect to 'tot' rect coordinates */
1221         WM_operator_properties_border_to_rctf(op, &rect);
1222         UI_view2d_region_to_view_rctf(v2d, &rect, &rect);
1223         
1224         /* check if zooming in/out view */
1225         gesture_mode = RNA_int_get(op->ptr, "gesture_mode");
1226         
1227         if (gesture_mode == GESTURE_MODAL_IN) {
1228                 /* zoom in: 
1229                  *      - 'cur' rect will be defined by the coordinates of the border region 
1230                  *      - just set the 'cur' rect to have the same coordinates as the border region
1231                  *        if zoom is allowed to be changed
1232                  */
1233                 if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) {
1234                         cur_new.xmin = rect.xmin;
1235                         cur_new.xmax = rect.xmax;
1236                 }
1237                 if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) {
1238                         cur_new.ymin = rect.ymin;
1239                         cur_new.ymax = rect.ymax;
1240                 }
1241         }
1242         else { /* if (gesture_mode == GESTURE_MODAL_OUT) */
1243
1244                 /* zoom out:
1245                  *      - the current 'cur' rect coordinates are going to end up where the 'rect' ones are,
1246                  *        but the 'cur' rect coordinates will need to be adjusted to take in more of the view
1247                  *      - calculate zoom factor, and adjust using center-point
1248                  */
1249                 float zoom, center, size;
1250                 
1251                 /* TODO: is this zoom factor calculation valid? It seems to produce same results every time... */
1252                 if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) {
1253                         size = BLI_rctf_size_x(&cur_new);
1254                         zoom = size / BLI_rctf_size_x(&rect);
1255                         center = BLI_rctf_cent_x(&cur_new);
1256                         
1257                         cur_new.xmin = center - (size * zoom);
1258                         cur_new.xmax = center + (size * zoom);
1259                 }
1260                 if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) {
1261                         size = BLI_rctf_size_y(&cur_new);
1262                         zoom = size / BLI_rctf_size_y(&rect);
1263                         center = BLI_rctf_cent_y(&cur_new);
1264                         
1265                         cur_new.ymin = center - (size * zoom);
1266                         cur_new.ymax = center + (size * zoom);
1267                 }
1268         }
1269         
1270         UI_view2d_smooth_view(C, ar, &cur_new, smooth_viewtx);
1271         
1272         return OPERATOR_FINISHED;
1273
1274
1275 static void VIEW2D_OT_zoom_border(wmOperatorType *ot)
1276 {
1277         /* identifiers */
1278         ot->name = "Zoom to Border";
1279         ot->description = "Zoom in the view to the nearest item contained in the border";
1280         ot->idname = "VIEW2D_OT_zoom_border";
1281         
1282         /* api callbacks */
1283         ot->invoke = WM_border_select_invoke;
1284         ot->exec = view_borderzoom_exec;
1285         ot->modal = WM_border_select_modal;
1286         ot->cancel = WM_border_select_cancel;
1287         
1288         ot->poll = view_zoom_poll;
1289         
1290         /* rna */
1291         WM_operator_properties_gesture_border(ot, false);
1292 }
1293
1294
1295 static int view2d_ndof_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1296 {
1297         if (event->type != NDOF_MOTION) {
1298                 return OPERATOR_CANCELLED;
1299         }
1300         else {
1301                 const wmNDOFMotionData *ndof = event->customdata;
1302
1303                 /* tune these until it feels right */
1304                 const float zoom_sensitivity = 0.5f;
1305                 const float speed = 10.0f;  /* match view3d ortho */
1306                 const bool has_translate = (ndof->tvec[0] && ndof->tvec[1]) && view_pan_poll(C);
1307                 const bool has_zoom = (ndof->tvec[2] != 0.0f) && view_zoom_poll(C);
1308
1309                 if (has_translate) {
1310                         if (view_pan_init(C, op)) {
1311                                 v2dViewPanData *vpd;
1312                                 float pan_vec[3];
1313
1314                                 WM_event_ndof_pan_get(ndof, pan_vec, false);
1315
1316                                 pan_vec[0] *= speed;
1317                                 pan_vec[1] *= speed;
1318
1319                                 vpd = op->customdata;
1320
1321                                 view_pan_apply_ex(C, vpd, pan_vec[0], pan_vec[1]);
1322
1323                                 view_pan_exit(op);
1324                         }
1325                 }
1326
1327                 if (has_zoom) {
1328                         if (view_zoomdrag_init(C, op)) {
1329                                 v2dViewZoomData *vzd;
1330                                 float zoom_factor = zoom_sensitivity * ndof->dt * -ndof->tvec[2];
1331
1332                                 bool do_zoom_xy[2];
1333
1334                                 if (U.ndof_flag & NDOF_ZOOM_INVERT)
1335                                         zoom_factor = -zoom_factor;
1336
1337                                 view_zoom_axis_lock_defaults(C, do_zoom_xy);
1338
1339                                 vzd = op->customdata;
1340
1341                                 view_zoomstep_apply_ex(C, vzd, false,
1342                                                        do_zoom_xy[0] ? zoom_factor : 0.0f,
1343                                                        do_zoom_xy[1] ? zoom_factor : 0.0f);
1344
1345                                 view_zoomstep_exit(op);
1346                         }
1347                 }
1348
1349                 return OPERATOR_FINISHED;
1350         }
1351 }
1352
1353 static void VIEW2D_OT_ndof(wmOperatorType *ot)
1354 {
1355         /* identifiers */
1356         ot->name = "NDOF Pan/Zoom";
1357         ot->idname = "VIEW2D_OT_ndof";
1358         ot->description = "Use a 3D mouse device to pan/zoom the view";
1359
1360         /* api callbacks */
1361         ot->invoke = view2d_ndof_invoke;
1362         ot->poll = view2d_poll;
1363
1364         /* flags */
1365         ot->flag = OPTYPE_LOCK_BYPASS;
1366 }
1367
1368 /* ********************************************************* */
1369 /* SMOOTH VIEW */
1370
1371 struct SmoothView2DStore {
1372         rctf orig_cur, new_cur;
1373
1374         double time_allowed;
1375 };
1376
1377 /**
1378  * function to get a factor out of a rectangle
1379  *
1380  * note: this doesn't always work as well as it might because the target size
1381  *       may not be reached because of clamping the desired rect, we _could_
1382  *       attempt to clamp the rect before working out the zoom factor but its
1383  *       not really worthwhile for the few cases this happens.
1384  */
1385 static float smooth_view_rect_to_fac(const rctf *rect_a, const rctf *rect_b)
1386 {
1387         const float size_a[2] = {BLI_rctf_size_x(rect_a),
1388                                  BLI_rctf_size_y(rect_a)};
1389         const float size_b[2] = {BLI_rctf_size_x(rect_b),
1390                                  BLI_rctf_size_y(rect_b)};
1391         const float cent_a[2] = {BLI_rctf_cent_x(rect_a),
1392                                  BLI_rctf_cent_y(rect_a)};
1393         const float cent_b[2] = {BLI_rctf_cent_x(rect_b),
1394                                  BLI_rctf_cent_y(rect_b)};
1395
1396         float fac_max = 0.0f;
1397         float tfac;
1398
1399         int i;
1400
1401         for (i = 0; i < 2; i++) {
1402                 /* axis translation normalized to scale */
1403                 tfac = fabsf(cent_a[i] - cent_b[i]) / min_ff(size_a[i], size_b[i]);
1404                 fac_max = max_ff(fac_max, tfac);
1405                 if (fac_max >= 1.0f) break;
1406
1407                 /* axis scale difference, x2 so doubling or half gives 1.0f */
1408                 tfac = (1.0f - (min_ff(size_a[i], size_b[i]) / max_ff(size_a[i], size_b[i]))) * 2.0f;
1409                 fac_max = max_ff(fac_max, tfac);
1410                 if (fac_max >= 1.0f) break;
1411         }
1412         return min_ff(fac_max, 1.0f);
1413 }
1414
1415 /* will start timer if appropriate */
1416 /* the arguments are the desired situation */
1417 void UI_view2d_smooth_view(
1418         bContext *C, ARegion *ar,
1419         const rctf *cur, const int smooth_viewtx)
1420 {
1421         wmWindowManager *wm = CTX_wm_manager(C);
1422         wmWindow *win = CTX_wm_window(C);
1423
1424         View2D *v2d = &ar->v2d;
1425         struct SmoothView2DStore sms = {{0}};
1426         bool ok = false;
1427         float fac = 1.0f;
1428
1429         /* initialize sms */
1430         sms.new_cur = v2d->cur;
1431
1432         /* store the options we want to end with */
1433         if (cur) sms.new_cur = *cur;
1434
1435         if (cur) {
1436                 fac = smooth_view_rect_to_fac(&v2d->cur, cur);
1437         }
1438
1439         if (smooth_viewtx && fac > FLT_EPSILON) {
1440                 bool changed = false;
1441
1442                 if (BLI_rctf_compare(&sms.new_cur, &v2d->cur, FLT_EPSILON) == false)
1443                         changed = true;
1444
1445                 /* The new view is different from the old one
1446                  * so animate the view */
1447                 if (changed) {
1448                         sms.orig_cur = v2d->cur;
1449
1450                         sms.time_allowed = (double)smooth_viewtx / 1000.0;
1451
1452                         /* scale the time allowed the change in view */
1453                         sms.time_allowed *= (double)fac;
1454
1455                         /* keep track of running timer! */
1456                         if (v2d->sms == NULL)
1457                                 v2d->sms = MEM_mallocN(sizeof(struct SmoothView2DStore), "smoothview v2d");
1458                         *v2d->sms = sms;
1459                         if (v2d->smooth_timer)
1460                                 WM_event_remove_timer(wm, win, v2d->smooth_timer);
1461                         /* TIMER1 is hardcoded in keymap */
1462                         v2d->smooth_timer = WM_event_add_timer(wm, win, TIMER1, 1.0 / 100.0); /* max 30 frs/sec */
1463
1464                         ok = true;
1465                 }
1466         }
1467
1468         /* if we get here nothing happens */
1469         if (ok == false) {
1470                 v2d->cur = sms.new_cur;
1471
1472                 UI_view2d_curRect_validate(v2d);
1473                 ED_region_tag_redraw(ar);
1474                 UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
1475         }
1476 }
1477
1478 /* only meant for timer usage */
1479 static int view2d_smoothview_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
1480 {
1481         ARegion *ar = CTX_wm_region(C);
1482         View2D *v2d = &ar->v2d;
1483         struct SmoothView2DStore *sms = v2d->sms;
1484         float step;
1485
1486         /* escape if not our timer */
1487         if (v2d->smooth_timer == NULL || v2d->smooth_timer != event->customdata)
1488                 return OPERATOR_PASS_THROUGH;
1489
1490         if (sms->time_allowed != 0.0)
1491                 step = (float)((v2d->smooth_timer->duration) / sms->time_allowed);
1492         else
1493                 step = 1.0f;
1494
1495         /* end timer */
1496         if (step >= 1.0f) {
1497                 v2d->cur = sms->new_cur;
1498
1499                 MEM_freeN(v2d->sms);
1500                 v2d->sms = NULL;
1501
1502                 WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), v2d->smooth_timer);
1503                 v2d->smooth_timer = NULL;
1504         }
1505         else {
1506                 /* ease in/out */
1507                 step = (3.0f * step * step - 2.0f * step * step * step);
1508
1509                 BLI_rctf_interp(&v2d->cur, &sms->orig_cur, &sms->new_cur, step);
1510         }
1511
1512         UI_view2d_curRect_validate(v2d);
1513         UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
1514         ED_region_tag_redraw(ar);
1515
1516         if (v2d->sms == NULL) {
1517                 UI_view2d_zoom_cache_reset();
1518         }
1519
1520         return OPERATOR_FINISHED;
1521 }
1522
1523 static void VIEW2D_OT_smoothview(wmOperatorType *ot)
1524 {
1525         /* identifiers */
1526         ot->name = "Smooth View 2D";
1527         ot->description = "";
1528         ot->idname = "VIEW2D_OT_smoothview";
1529
1530         /* api callbacks */
1531         ot->invoke = view2d_smoothview_invoke;
1532         ot->poll = view2d_poll;
1533
1534         /* flags */
1535         ot->flag = OPTYPE_INTERNAL;
1536
1537         /* rna */
1538         WM_operator_properties_gesture_border(ot, false);
1539 }
1540
1541 /* ********************************************************* */
1542 /* SCROLLERS */
1543
1544 /**
1545  * Scrollers should behave in the following ways, when clicked on with LMB (and dragged):
1546  * -# 'Handles' on end of 'bubble' - when the axis that the scroller represents is zoomable,
1547  *    enlarge 'cur' rect on the relevant side.
1548  * -# 'Bubble'/'bar' - just drag, and bar should move with mouse (view pans opposite).
1549  *
1550  * In order to make sure this works, each operator must define the following RNA-Operator Props:
1551  * - `deltax, deltay` - define how much to move view by (relative to zoom-correction factor)
1552  */
1553
1554 /* customdata for scroller-invoke data */
1555 typedef struct v2dScrollerMove {
1556         View2D *v2d;            /* View2D data that this operation affects */
1557         ARegion *ar;            /* region that the scroller is in */
1558         
1559         short scroller;         /* scroller that mouse is in ('h' or 'v') */
1560         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?)
1561         
1562         float fac;              /* view adjustment factor, based on size of region */
1563         float fac_round;        /* for pixel rounding (avoid visible UI jitter) */
1564         float delta;            /* amount moved by mouse on axis of interest */
1565         
1566         float scrollbarwidth;   /* width of the scrollbar itself, used for page up/down clicks */
1567         int scrollbar_orig;      /* initial location of scrollbar x/y, mouse relative */
1568         
1569         int lastx, lasty;       /* previous mouse coordinates (in screen coordinates) for determining movement */
1570 } v2dScrollerMove;
1571
1572
1573 /**
1574  * #View2DScrollers is typedef'd in UI_view2d.h
1575  * This is a CUT DOWN VERSION of the 'real' version, which is defined in view2d.c, as we only need focus bubble info
1576  *
1577  * \warning: The start of this struct must not change, so that it stays in sync with the 'real' version
1578  * For now, we don't need to have a separate (internal) header for structs like this...
1579  */
1580 struct View2DScrollers {        
1581         /* focus bubbles */
1582         int vert_min, vert_max; /* vertical scrollbar */
1583         int hor_min, hor_max;   /* horizontal scrollbar */
1584 };
1585
1586 /* quick enum for vsm->zone (scroller handles) */
1587 enum {
1588         SCROLLHANDLE_MIN = -1,
1589         SCROLLHANDLE_BAR,
1590         SCROLLHANDLE_MAX,
1591         SCROLLHANDLE_MIN_OUTSIDE,
1592         SCROLLHANDLE_MAX_OUTSIDE
1593 } /*eV2DScrollerHandle_Zone*/;
1594
1595 /* ------------------------ */
1596
1597 /**
1598  * Check if mouse is within scroller handle.
1599  *
1600  * \param mouse: relevant mouse coordinate in region space.
1601  * \param sc_min, sc_max: extents of scroller 'groove' (potential available space for scroller).
1602  * \param sh_min, sh_max: positions of scrollbar handles.
1603  */
1604 static short mouse_in_scroller_handle(int mouse, int sc_min, int sc_max, int sh_min, int sh_max)
1605 {
1606         bool in_min, in_max, in_bar, out_min, out_max, in_view = 1;
1607         
1608         /* firstly, check if 
1609          *      - 'bubble' fills entire scroller 
1610          *      - 'bubble' completely out of view on either side 
1611          */
1612         if ((sh_min <= sc_min) && (sh_max >= sc_max)) in_view = 0;
1613         if (sh_min == sh_max) {
1614                 if (sh_min <= sc_min) in_view = 0;
1615                 if (sh_max >= sc_max) in_view = 0;
1616         }
1617         else {
1618                 if (sh_max <= sc_min) in_view = 0;
1619                 if (sh_min >= sc_max) in_view = 0;
1620         }
1621         
1622         
1623         if (in_view == 0) {
1624                 return SCROLLHANDLE_BAR;
1625         }
1626         
1627         /* check if mouse is in or past either handle */
1628         /* TODO: check if these extents are still valid or not */
1629         in_max = ((mouse >= (sh_max - V2D_SCROLLER_HANDLE_SIZE)) && (mouse <= (sh_max + V2D_SCROLLER_HANDLE_SIZE)));
1630         in_min = ((mouse <= (sh_min + V2D_SCROLLER_HANDLE_SIZE)) && (mouse >= (sh_min - V2D_SCROLLER_HANDLE_SIZE)));
1631         in_bar = ((mouse < (sh_max - V2D_SCROLLER_HANDLE_SIZE)) && (mouse > (sh_min + V2D_SCROLLER_HANDLE_SIZE)));
1632         out_min = mouse < (sh_min - V2D_SCROLLER_HANDLE_SIZE);
1633         out_max = mouse > (sh_max + V2D_SCROLLER_HANDLE_SIZE);
1634         
1635         if (in_bar)
1636                 return SCROLLHANDLE_BAR;
1637         else if (in_max)
1638                 return SCROLLHANDLE_MAX;
1639         else if (in_min)
1640                 return SCROLLHANDLE_MIN;
1641         else if (out_min)
1642                 return SCROLLHANDLE_MIN_OUTSIDE;
1643         else if (out_max)
1644                 return SCROLLHANDLE_MAX_OUTSIDE;
1645         
1646         /* unlikely to happen, though we just cover it in case */
1647         return SCROLLHANDLE_BAR;
1648
1649
1650 /* initialize customdata for scroller manipulation operator */
1651 static void scroller_activate_init(bContext *C, wmOperator *op, const wmEvent *event, short in_scroller)
1652 {
1653         v2dScrollerMove *vsm;
1654         View2DScrollers *scrollers;
1655         ARegion *ar = CTX_wm_region(C);
1656         View2D *v2d = &ar->v2d;
1657         rctf tot_cur_union;
1658         float mask_size;
1659         
1660         /* set custom-data for operator */
1661         vsm = MEM_callocN(sizeof(v2dScrollerMove), "v2dScrollerMove");
1662         op->customdata = vsm;
1663         
1664         /* set general data */
1665         vsm->v2d = v2d;
1666         vsm->ar = ar;
1667         vsm->scroller = in_scroller;
1668
1669         /* store mouse-coordinates, and convert mouse/screen coordinates to region coordinates */
1670         vsm->lastx = event->x;
1671         vsm->lasty = event->y;
1672         /* 'zone' depends on where mouse is relative to bubble 
1673          *      - zooming must be allowed on this axis, otherwise, default to pan
1674          */
1675         scrollers = UI_view2d_scrollers_calc(C, v2d, V2D_ARG_DUMMY, V2D_ARG_DUMMY, V2D_ARG_DUMMY, V2D_ARG_DUMMY);
1676
1677         /* use a union of 'cur' & 'tot' incase the current view is far outside 'tot'.
1678          * In this cases moving the scroll bars has far too little effect and the view can get stuck [#31476] */
1679         tot_cur_union = v2d->tot;
1680         BLI_rctf_union(&tot_cur_union, &v2d->cur);
1681
1682         if (in_scroller == 'h') {
1683                 /* horizontal scroller - calculate adjustment factor first */
1684                 mask_size = (float)BLI_rcti_size_x(&v2d->hor);
1685                 vsm->fac = BLI_rctf_size_x(&tot_cur_union) / mask_size;
1686
1687                 /* pixel rounding */
1688                 vsm->fac_round = (BLI_rctf_size_x(&v2d->cur)) / (float)(BLI_rcti_size_x(&ar->winrct) + 1);
1689
1690                 /* get 'zone' (i.e. which part of scroller is activated) */
1691                 vsm->zone = mouse_in_scroller_handle(event->mval[0],
1692                                                      v2d->hor.xmin, v2d->hor.xmax,
1693                                                      scrollers->hor_min, scrollers->hor_max);
1694                 
1695                 if ((v2d->keepzoom & V2D_LOCKZOOM_X) && ELEM(vsm->zone, SCROLLHANDLE_MIN, SCROLLHANDLE_MAX)) {
1696                         /* default to scroll, as handles not usable */
1697                         vsm->zone = SCROLLHANDLE_BAR;
1698                 }
1699
1700                 vsm->scrollbarwidth = scrollers->hor_max - scrollers->hor_min;
1701                 vsm->scrollbar_orig = ((scrollers->hor_max + scrollers->hor_min) / 2) + ar->winrct.xmin;
1702         }
1703         else {
1704                 /* vertical scroller - calculate adjustment factor first */
1705                 mask_size = (float)BLI_rcti_size_y(&v2d->vert);
1706                 vsm->fac = BLI_rctf_size_y(&tot_cur_union) / mask_size;
1707                 
1708                 /* pixel rounding */
1709                 vsm->fac_round = (BLI_rctf_size_y(&v2d->cur)) / (float)(BLI_rcti_size_y(&ar->winrct) + 1);
1710
1711                 /* get 'zone' (i.e. which part of scroller is activated) */
1712                 vsm->zone = mouse_in_scroller_handle(event->mval[1],
1713                                                      v2d->vert.ymin, v2d->vert.ymax,
1714                                                      scrollers->vert_min, scrollers->vert_max);
1715                         
1716                 if ((v2d->keepzoom & V2D_LOCKZOOM_Y) && ELEM(vsm->zone, SCROLLHANDLE_MIN, SCROLLHANDLE_MAX)) {
1717                         /* default to scroll, as handles not usable */
1718                         vsm->zone = SCROLLHANDLE_BAR;
1719                 }
1720                 
1721                 vsm->scrollbarwidth = scrollers->vert_max - scrollers->vert_min;
1722                 vsm->scrollbar_orig = ((scrollers->vert_max + scrollers->vert_min) / 2) + ar->winrct.ymin;
1723         }
1724         
1725         UI_view2d_scrollers_free(scrollers);
1726         ED_region_tag_redraw(ar);
1727 }
1728
1729 /* cleanup temp customdata  */
1730 static void scroller_activate_exit(bContext *C, wmOperator *op)
1731 {
1732         if (op->customdata) {
1733                 v2dScrollerMove *vsm = op->customdata;
1734
1735                 vsm->v2d->scroll_ui &= ~(V2D_SCROLL_H_ACTIVE | V2D_SCROLL_V_ACTIVE);
1736                 
1737                 MEM_freeN(op->customdata);
1738                 op->customdata = NULL;
1739                 
1740                 ED_region_tag_redraw(CTX_wm_region(C));
1741         }
1742 }
1743
1744 static void scroller_activate_cancel(bContext *C, wmOperator *op)
1745 {
1746         scroller_activate_exit(C, op);
1747 }
1748
1749 /* apply transform to view (i.e. adjust 'cur' rect) */
1750 static void scroller_activate_apply(bContext *C, wmOperator *op)
1751 {
1752         v2dScrollerMove *vsm = op->customdata;
1753         View2D *v2d = vsm->v2d;
1754         float temp;
1755         
1756         /* calculate amount to move view by */
1757         temp = vsm->fac * vsm->delta;
1758
1759         /* round to pixel */
1760         temp = roundf(temp / vsm->fac_round) * vsm->fac_round;
1761         
1762         /* type of movement */
1763         switch (vsm->zone) {
1764                 case SCROLLHANDLE_MIN:
1765                         /* only expand view on axis if zoom is allowed */
1766                         if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X))
1767                                 v2d->cur.xmin -= temp;
1768                         if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y))
1769                                 v2d->cur.ymin -= temp;
1770                         break;
1771                         
1772                 case SCROLLHANDLE_MAX:
1773                         
1774                         /* only expand view on axis if zoom is allowed */
1775                         if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X))
1776                                 v2d->cur.xmax += temp;
1777                         if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y))
1778                                 v2d->cur.ymax += temp;
1779                         break;
1780                         
1781                 case SCROLLHANDLE_MIN_OUTSIDE:
1782                 case SCROLLHANDLE_MAX_OUTSIDE:
1783                 case SCROLLHANDLE_BAR:
1784                 default:
1785                         /* only move view on an axis if panning is allowed */
1786                         if ((vsm->scroller == 'h') && !(v2d->keepofs & V2D_LOCKOFS_X)) {
1787                                 v2d->cur.xmin += temp;
1788                                 v2d->cur.xmax += temp;
1789                         }
1790                         if ((vsm->scroller == 'v') && !(v2d->keepofs & V2D_LOCKOFS_Y)) {
1791                                 v2d->cur.ymin += temp;
1792                                 v2d->cur.ymax += temp;
1793                         }
1794                         break;
1795                         
1796         }
1797         
1798         /* validate that view is in valid configuration after this operation */
1799         UI_view2d_curRect_validate(v2d);
1800         
1801         /* request updates to be done... */
1802         ED_region_tag_redraw(vsm->ar);
1803         UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
1804 }
1805
1806 /**
1807  * Handle user input for scrollers - calculations of mouse-movement need to be done here,
1808  * not in the apply callback!
1809  */
1810 static int scroller_activate_modal(bContext *C, wmOperator *op, const wmEvent *event)
1811 {
1812         v2dScrollerMove *vsm = op->customdata;
1813         
1814         /* execute the events */
1815         switch (event->type) {
1816                 case MOUSEMOVE:
1817                 {
1818                         /* calculate new delta transform, then store mouse-coordinates for next-time */
1819                         if (ELEM(vsm->zone, SCROLLHANDLE_BAR, SCROLLHANDLE_MAX)) {
1820                                 /* if using bar (i.e. 'panning') or 'max' zoom widget */
1821                                 switch (vsm->scroller) {
1822                                         case 'h': /* horizontal scroller - so only horizontal movement ('cur' moves opposite to mouse) */
1823                                                 vsm->delta = (float)(event->x - vsm->lastx);
1824                                                 break;
1825                                         case 'v': /* vertical scroller - so only vertical movement ('cur' moves opposite to mouse) */
1826                                                 vsm->delta = (float)(event->y - vsm->lasty);
1827                                                 break;
1828                                 }
1829                         }
1830                         else if (vsm->zone == SCROLLHANDLE_MIN) {
1831                                 /* using 'min' zoom widget */
1832                                 switch (vsm->scroller) {
1833                                         case 'h': /* horizontal scroller - so only horizontal movement ('cur' moves with mouse) */
1834                                                 vsm->delta = (float)(vsm->lastx - event->x);
1835                                                 break;
1836                                         case 'v': /* vertical scroller - so only vertical movement ('cur' moves with to mouse) */
1837                                                 vsm->delta = (float)(vsm->lasty - event->y);
1838                                                 break;
1839                                 }
1840                         }
1841                         
1842                         /* store previous coordinates */
1843                         vsm->lastx = event->x;
1844                         vsm->lasty = event->y;
1845                         
1846                         scroller_activate_apply(C, op);
1847                         break;
1848                 }
1849                 case LEFTMOUSE:
1850                 case MIDDLEMOUSE:
1851                         if (event->val == KM_RELEASE) {
1852                                 /* single-click was in empty space outside bubble, so scroll by 1 'page' */
1853                                 if (ELEM(vsm->zone, SCROLLHANDLE_MIN_OUTSIDE, SCROLLHANDLE_MAX_OUTSIDE)) {
1854                                         if (vsm->zone == SCROLLHANDLE_MIN_OUTSIDE)
1855                                                 vsm->delta = -vsm->scrollbarwidth * 0.8f;
1856                                         else if (vsm->zone == SCROLLHANDLE_MAX_OUTSIDE)
1857                                                 vsm->delta = vsm->scrollbarwidth * 0.8f;
1858                                         
1859                                         scroller_activate_apply(C, op);
1860                                         scroller_activate_exit(C, op);
1861                                         return OPERATOR_FINISHED;
1862                                 }
1863                                 
1864                                 /* otherwise, end the drag action  */
1865                                 if (vsm->lastx || vsm->lasty) {
1866                                         scroller_activate_exit(C, op);
1867                                         return OPERATOR_FINISHED;
1868                                 }
1869                         }
1870                         break;
1871
1872         }
1873
1874         return OPERATOR_RUNNING_MODAL;
1875 }
1876
1877
1878 /* a click (or click drag in progress) should have occurred, so check if it happened in scrollbar */
1879 static int scroller_activate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1880 {
1881         ARegion *ar = CTX_wm_region(C);
1882         View2D *v2d = &ar->v2d;
1883         short in_scroller = 0;
1884                 
1885         /* check if mouse in scrollbars, if they're enabled */
1886         in_scroller = UI_view2d_mouse_in_scrollers(C, v2d, event->x, event->y);
1887         
1888         /* if in a scroller, init customdata then set modal handler which will catch mousedown to start doing useful stuff */
1889         if (in_scroller) {
1890                 v2dScrollerMove *vsm;
1891                 
1892                 /* initialize customdata */
1893                 scroller_activate_init(C, op, event, in_scroller);
1894                 vsm = (v2dScrollerMove *)op->customdata;
1895                 
1896                 /* support for quick jump to location - gtk and qt do this on linux */
1897                 if (event->type == MIDDLEMOUSE) {
1898                         switch (vsm->scroller) {
1899                                 case 'h': /* horizontal scroller - so only horizontal movement ('cur' moves opposite to mouse) */
1900                                         vsm->delta = (float)(event->x - vsm->scrollbar_orig);
1901                                         break;
1902                                 case 'v': /* vertical scroller - so only vertical movement ('cur' moves opposite to mouse) */
1903                                         vsm->delta = (float)(event->y - vsm->scrollbar_orig);
1904                                         break;
1905                         }
1906                         scroller_activate_apply(C, op);
1907
1908                         vsm->zone = SCROLLHANDLE_BAR;
1909                 }
1910
1911                 /* check if zoom zones are inappropriate (i.e. zoom widgets not shown), so cannot continue
1912                  * NOTE: see view2d.c for latest conditions, and keep this in sync with that
1913                  */
1914                 if (ELEM(vsm->zone, SCROLLHANDLE_MIN, SCROLLHANDLE_MAX)) {
1915                         if (((vsm->scroller == 'h') && (v2d->scroll & V2D_SCROLL_SCALE_HORIZONTAL) == 0) ||
1916                             ((vsm->scroller == 'v') && (v2d->scroll & V2D_SCROLL_SCALE_VERTICAL) == 0))
1917                         {
1918                                 /* switch to bar (i.e. no scaling gets handled) */
1919                                 vsm->zone = SCROLLHANDLE_BAR;
1920                         }
1921                 }
1922                 
1923                 /* check if zone is inappropriate (i.e. 'bar' but panning is banned), so cannot continue */
1924                 if (vsm->zone == SCROLLHANDLE_BAR) {
1925                         if (((vsm->scroller == 'h') && (v2d->keepofs & V2D_LOCKOFS_X)) ||
1926                             ((vsm->scroller == 'v') && (v2d->keepofs & V2D_LOCKOFS_Y)))
1927                         {
1928                                 /* free customdata initialized */
1929                                 scroller_activate_exit(C, op);
1930                                 
1931                                 /* can't catch this event for ourselves, so let it go to someone else? */
1932                                 return OPERATOR_PASS_THROUGH;
1933                         }
1934                 }
1935                 
1936                 /* zone is also inappropriate if scroller is not visible... */
1937                 if (((vsm->scroller == 'h') && (v2d->scroll & (V2D_SCROLL_HORIZONTAL_FULLR))) ||
1938                     ((vsm->scroller == 'v') && (v2d->scroll & (V2D_SCROLL_VERTICAL_FULLR))) )
1939                 {
1940                         /* free customdata initialized */
1941                         scroller_activate_exit(C, op);
1942                                 
1943                         /* can't catch this event for ourselves, so let it go to someone else? */
1944                         /* XXX note: if handlers use mask rect to clip input, input will fail for this case */
1945                         return OPERATOR_PASS_THROUGH;
1946                 }
1947                 
1948                 /* activate the scroller */
1949                 if (vsm->scroller == 'h')
1950                         v2d->scroll_ui |= V2D_SCROLL_H_ACTIVE;
1951                 else
1952                         v2d->scroll_ui |= V2D_SCROLL_V_ACTIVE;
1953                 
1954                 /* still ok, so can add */
1955                 WM_event_add_modal_handler(C, op);
1956                 return OPERATOR_RUNNING_MODAL;
1957         }
1958         else {
1959                 /* not in scroller, so nothing happened... (pass through let's something else catch event) */
1960                 return OPERATOR_PASS_THROUGH;
1961         }
1962 }
1963
1964 /* LMB-Drag in Scrollers - not repeatable operator! */
1965 static void VIEW2D_OT_scroller_activate(wmOperatorType *ot)
1966 {
1967         /* identifiers */
1968         ot->name = "Scroller Activate";
1969         ot->description = "Scroll view by mouse click and drag";
1970         ot->idname = "VIEW2D_OT_scroller_activate";
1971
1972         /* flags */
1973         ot->flag = OPTYPE_BLOCKING;
1974         
1975         /* api callbacks */
1976         ot->invoke = scroller_activate_invoke;
1977         ot->modal = scroller_activate_modal;
1978         ot->cancel = scroller_activate_cancel;
1979
1980         ot->poll = view2d_poll;
1981 }
1982
1983 /* ********************************************************* */
1984 /* RESET */
1985
1986 static int reset_exec(bContext *C, wmOperator *UNUSED(op))
1987 {
1988         uiStyle *style = UI_style_get();
1989         ARegion *ar = CTX_wm_region(C);
1990         View2D *v2d = &ar->v2d;
1991         int winx, winy;
1992
1993         /* zoom 1.0 */
1994         winx = (float)(BLI_rcti_size_x(&v2d->mask) + 1);
1995         winy = (float)(BLI_rcti_size_y(&v2d->mask) + 1);
1996
1997         v2d->cur.xmax = v2d->cur.xmin + winx;
1998         v2d->cur.ymax = v2d->cur.ymin + winy;
1999         
2000         /* align */
2001         if (v2d->align) {
2002                 /* posx and negx flags are mutually exclusive, so watch out */
2003                 if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) {
2004                         v2d->cur.xmax = 0.0f;
2005                         v2d->cur.xmin = -winx * style->panelzoom;
2006                 }
2007                 else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) {
2008                         v2d->cur.xmax = winx * style->panelzoom;
2009                         v2d->cur.xmin = 0.0f;
2010                 }
2011
2012                 /* - posx and negx flags are mutually exclusive, so watch out */
2013                 if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) {
2014                         v2d->cur.ymax = 0.0f;
2015                         v2d->cur.ymin = -winy * style->panelzoom;
2016                 }
2017                 else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) {
2018                         v2d->cur.ymax = winy * style->panelzoom;
2019                         v2d->cur.ymin = 0.0f;
2020                 }
2021         }
2022
2023         /* validate that view is in valid configuration after this operation */
2024         UI_view2d_curRect_validate(v2d);
2025         
2026         /* request updates to be done... */
2027         ED_region_tag_redraw(ar);
2028         UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
2029         
2030         UI_view2d_zoom_cache_reset();
2031
2032         return OPERATOR_FINISHED;
2033 }
2034
2035 static void VIEW2D_OT_reset(wmOperatorType *ot)
2036 {
2037         /* identifiers */
2038         ot->name = "Reset View";
2039         ot->description = "Reset the view";
2040         ot->idname = "VIEW2D_OT_reset";
2041         
2042         /* api callbacks */
2043         ot->exec = reset_exec;
2044         ot->poll = view2d_poll;
2045 }
2046  
2047 /* ********************************************************* */
2048 /* Registration */
2049
2050 void ED_operatortypes_view2d(void)
2051 {
2052         WM_operatortype_append(VIEW2D_OT_pan);
2053         
2054         WM_operatortype_append(VIEW2D_OT_scroll_left);
2055         WM_operatortype_append(VIEW2D_OT_scroll_right);
2056         WM_operatortype_append(VIEW2D_OT_scroll_up);
2057         WM_operatortype_append(VIEW2D_OT_scroll_down);
2058         
2059         WM_operatortype_append(VIEW2D_OT_zoom_in);
2060         WM_operatortype_append(VIEW2D_OT_zoom_out);
2061         
2062         WM_operatortype_append(VIEW2D_OT_zoom);
2063         WM_operatortype_append(VIEW2D_OT_zoom_border);
2064
2065         WM_operatortype_append(VIEW2D_OT_ndof);
2066
2067         WM_operatortype_append(VIEW2D_OT_smoothview);
2068         
2069         WM_operatortype_append(VIEW2D_OT_scroller_activate);
2070
2071         WM_operatortype_append(VIEW2D_OT_reset);
2072 }
2073
2074 void ED_keymap_view2d(wmKeyConfig *keyconf)
2075 {
2076         wmKeyMap *keymap = WM_keymap_find(keyconf, "View2D", 0, 0);
2077         wmKeyMapItem *kmi;
2078
2079         /* scrollers */
2080         WM_keymap_add_item(keymap, "VIEW2D_OT_scroller_activate", LEFTMOUSE, KM_PRESS, 0, 0);
2081         WM_keymap_add_item(keymap, "VIEW2D_OT_scroller_activate", MIDDLEMOUSE, KM_PRESS, 0, 0);
2082
2083         /* pan/scroll */
2084         WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MIDDLEMOUSE, KM_PRESS, 0, 0);
2085         WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MIDDLEMOUSE, KM_PRESS, KM_SHIFT, 0);
2086         
2087         WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MOUSEPAN, 0, 0, 0);
2088         
2089         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_right", WHEELDOWNMOUSE, KM_PRESS, KM_CTRL, 0);
2090         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_left", WHEELUPMOUSE, KM_PRESS, KM_CTRL, 0);
2091         
2092         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_down", WHEELDOWNMOUSE, KM_PRESS, KM_SHIFT, 0);
2093         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_up", WHEELUPMOUSE, KM_PRESS, KM_SHIFT, 0);
2094         
2095         WM_keymap_add_item(keymap, "VIEW2D_OT_ndof", NDOF_MOTION, 0, 0, 0);
2096
2097         /* zoom - single step */
2098         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_out", WHEELOUTMOUSE, KM_PRESS, 0, 0);
2099         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_in", WHEELINMOUSE, KM_PRESS, 0, 0);
2100         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_out", PADMINUS, KM_PRESS, 0, 0);
2101         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_in", PADPLUSKEY, KM_PRESS, 0, 0);
2102         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MOUSEPAN, 0, KM_CTRL, 0);
2103         
2104         WM_keymap_verify_item(keymap, "VIEW2D_OT_smoothview", TIMER1, KM_ANY, KM_ANY, 0);
2105
2106         /* scroll up/down - no modifiers, only when zoom fails */
2107         /* these may fail if zoom is disallowed, in which case they should pass on event */
2108         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_down", WHEELDOWNMOUSE, KM_PRESS, 0, 0);
2109         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_up", WHEELUPMOUSE, KM_PRESS, 0, 0);
2110         /* these may be necessary if vertical scroll is disallowed */
2111         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_right", WHEELDOWNMOUSE, KM_PRESS, 0, 0);
2112         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_left", WHEELUPMOUSE, KM_PRESS, 0, 0);
2113         
2114         /* alternatives for page up/down to scroll */
2115 #if 0 // XXX disabled, since this causes conflicts with hotkeys in animation editors
2116         /* scroll up/down may fall through to left/right */
2117         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_down", PAGEDOWNKEY, KM_PRESS, 0, 0);
2118         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_up", PAGEUPKEY, KM_PRESS, 0, 0);
2119         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_right", PAGEDOWNKEY, KM_PRESS, 0, 0);
2120         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_left", PAGEUPKEY, KM_PRESS, 0, 0);
2121         /* shift for moving view left/right with page up/down */
2122         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_right", PAGEDOWNKEY, KM_PRESS, KM_SHIFT, 0);
2123         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_left", PAGEUPKEY, KM_PRESS, KM_SHIFT, 0);
2124 #endif
2125         
2126         /* zoom - drag */
2127         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MIDDLEMOUSE, KM_PRESS, KM_CTRL, 0);
2128         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MOUSEZOOM, 0, 0, 0);
2129         
2130         /* borderzoom - drag */
2131         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_border", BKEY, KM_PRESS, KM_SHIFT, 0);
2132
2133         /* Alternative keymap for buttons listview */
2134         keymap = WM_keymap_find(keyconf, "View2D Buttons List", 0, 0);
2135
2136         WM_keymap_add_item(keymap, "VIEW2D_OT_scroller_activate", LEFTMOUSE, KM_PRESS, 0, 0);
2137         WM_keymap_add_item(keymap, "VIEW2D_OT_scroller_activate", MIDDLEMOUSE, KM_PRESS, 0, 0);
2138
2139         WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MIDDLEMOUSE, KM_PRESS, 0, 0);
2140         WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MOUSEPAN, 0, 0, 0);
2141         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_down", WHEELDOWNMOUSE, KM_PRESS, 0, 0);
2142         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_up", WHEELUPMOUSE, KM_PRESS, 0, 0);
2143         
2144         kmi = WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_down", PAGEDOWNKEY, KM_PRESS, 0, 0);
2145         RNA_boolean_set(kmi->ptr, "page", true);
2146         kmi = WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_up", PAGEUPKEY, KM_PRESS, 0, 0);
2147         RNA_boolean_set(kmi->ptr, "page", true);
2148         
2149         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MIDDLEMOUSE, KM_PRESS, KM_CTRL, 0);
2150         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MOUSEZOOM, 0, 0, 0);
2151         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MOUSEPAN, 0, KM_CTRL, 0);
2152         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_out", PADMINUS, KM_PRESS, 0, 0);
2153         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_in", PADPLUSKEY, KM_PRESS, 0, 0);
2154         WM_keymap_add_item(keymap, "VIEW2D_OT_reset", HOMEKEY, KM_PRESS, 0, 0);
2155 }
2156