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