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