Fix [#20874] zoom to mouse only in 3d view
[blender-staging.git] / source / blender / editors / interface / view2d_ops.c
1 /**
2  * $Id$
3  *
4  * ***** BEGIN GPL LICENSE BLOCK *****
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version. 
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19  *
20  * The Original Code is Copyright (C) 2008 Blender Foundation.
21  * All rights reserved.
22  * 
23  * Contributor(s): Blender Foundation, Joshua Leung
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 #include <math.h>
29
30 #include "MEM_guardedalloc.h"
31
32 #include "DNA_scene_types.h"
33 #include "DNA_screen_types.h"
34 #include "DNA_space_types.h"
35 #include "DNA_userdef_types.h"
36 #include "DNA_vec_types.h"
37 #include "DNA_view2d_types.h"
38
39 #include "BLI_blenlib.h"
40
41 #include "BKE_context.h"
42 #include "BKE_utildefines.h"
43
44 #include "RNA_access.h"
45 #include "RNA_define.h"
46
47 #include "WM_api.h"
48 #include "WM_types.h"
49
50 #include "BIF_gl.h"
51
52 #include "ED_screen.h"
53
54 #include "UI_resources.h"
55 #include "UI_view2d.h"
56
57 static int view2d_poll(bContext *C)
58 {
59         ARegion *ar= CTX_wm_region(C);
60
61         return (ar != NULL) && (ar->v2d.flag & V2D_IS_INITIALISED);
62 }
63
64 /* ********************************************************* */
65 /* VIEW PANNING OPERATOR                                                                 */
66
67 /*      This group of operators come in several forms:
68  *              1) Modal 'dragging' with MMB - where movement of mouse dictates amount to pan view by
69  *              2) 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         View2D *v2d;                    /* view2d we're operating in */
82         
83         float facx, facy;               /* amount to move view relative to zoom */
84         
85                 /* options for version 1 */
86         int startx, starty;             /* mouse x/y values in window when operator was initiated */
87         int lastx, lasty;               /* previous x/y values of mouse in window */
88         
89         short in_scroller;              /* for MMB in scrollers (old feature in past, but now not that useful) */
90 } v2dViewPanData;
91  
92 /* initialise panning customdata */
93 static int view_pan_init(bContext *C, wmOperator *op)
94 {
95         ARegion *ar= CTX_wm_region(C);
96         v2dViewPanData *vpd;
97         View2D *v2d;
98         float winx, winy;
99         
100         /* regions now have v2d-data by default, so check for region */
101         if (ar == NULL)
102                 return 0;
103                 
104         /* check if panning is allowed at all */
105         v2d= &ar->v2d;
106         if ((v2d->keepofs & V2D_LOCKOFS_X) && (v2d->keepofs & V2D_LOCKOFS_Y))
107                 return 0;
108         
109         /* set custom-data for operator */
110         vpd= MEM_callocN(sizeof(v2dViewPanData), "v2dViewPanData");
111         op->customdata= vpd;
112         
113         /* set pointers to owners */
114         vpd->sc= CTX_wm_screen(C);
115         vpd->sa= CTX_wm_area(C);
116         vpd->v2d= v2d;
117         
118         /* calculate translation factor - based on size of view */
119         winx= (float)(ar->winrct.xmax - ar->winrct.xmin + 1);
120         winy= (float)(ar->winrct.ymax - ar->winrct.ymin + 1);
121         vpd->facx= (v2d->cur.xmax - v2d->cur.xmin) / winx;
122         vpd->facy= (v2d->cur.ymax - v2d->cur.ymin) / winy;
123         
124         return 1;
125 }
126
127 /* apply transform to view (i.e. adjust 'cur' rect) */
128 static void view_pan_apply(bContext *C, wmOperator *op)
129 {
130         v2dViewPanData *vpd= op->customdata;
131         View2D *v2d= vpd->v2d;
132         float dx, dy;
133         
134         /* calculate amount to move view by */
135         dx= vpd->facx * (float)RNA_int_get(op->ptr, "deltax");
136         dy= vpd->facy * (float)RNA_int_get(op->ptr, "deltay");
137         
138         /* only move view on an axis if change is allowed */
139         if ((v2d->keepofs & V2D_LOCKOFS_X)==0) {
140                 v2d->cur.xmin += dx;
141                 v2d->cur.xmax += dx;
142         }
143         if ((v2d->keepofs & V2D_LOCKOFS_Y)==0) {
144                 v2d->cur.ymin += dy;
145                 v2d->cur.ymax += dy;
146         }
147         
148         /* validate that view is in valid configuration after this operation */
149         UI_view2d_curRect_validate(v2d);
150         
151         /* request updates to be done... */
152         ED_area_tag_redraw(vpd->sa);
153         UI_view2d_sync(vpd->sc, vpd->sa, v2d, V2D_LOCK_COPY);
154         WM_event_add_mousemove(C);
155         
156         /* exceptions */
157         if(vpd->sa->spacetype==SPACE_OUTLINER) {
158                 SpaceOops *soops= vpd->sa->spacedata.first;
159                 soops->storeflag |= SO_TREESTORE_REDRAW;
160         }
161 }
162
163 /* cleanup temp customdata  */
164 static void view_pan_exit(bContext *C, wmOperator *op)
165 {
166         if (op->customdata) {
167                 MEM_freeN(op->customdata);
168                 op->customdata= NULL;                           
169         }
170
171  
172 /* ------------------ Modal Drag Version (1) ---------------------- */
173
174 /* for 'redo' only, with no user input */
175 static int view_pan_exec(bContext *C, wmOperator *op)
176 {
177         if (!view_pan_init(C, op))
178                 return OPERATOR_CANCELLED;
179         
180         view_pan_apply(C, op);
181         view_pan_exit(C, op);
182         return OPERATOR_FINISHED;
183 }
184
185 /* set up modal operator and relevant settings */
186 static int view_pan_invoke(bContext *C, wmOperator *op, wmEvent *event)
187 {
188         wmWindow *window= CTX_wm_window(C);
189         v2dViewPanData *vpd;
190         View2D *v2d;
191         
192         /* set up customdata */
193         if (!view_pan_init(C, op))
194                 return OPERATOR_PASS_THROUGH;
195         
196         vpd= op->customdata;
197         v2d= vpd->v2d;
198         
199         /* set initial settings */
200         vpd->startx= vpd->lastx= event->x;
201         vpd->starty= vpd->lasty= event->y;
202
203         if (event->type == MOUSEPAN) {
204                 RNA_int_set(op->ptr, "deltax", event->prevx - event->x);
205                 RNA_int_set(op->ptr, "deltay", event->prevy - event->y);
206                 
207                 view_pan_apply(C, op);
208                 view_pan_exit(C, op);
209                 return OPERATOR_FINISHED;
210         }
211         
212         RNA_int_set(op->ptr, "deltax", 0);
213         RNA_int_set(op->ptr, "deltay", 0);
214         
215         if (v2d->keepofs & V2D_LOCKOFS_X)
216                 WM_cursor_modal(window, BC_NS_SCROLLCURSOR);
217         else if (v2d->keepofs & V2D_LOCKOFS_Y)
218                 WM_cursor_modal(window, BC_EW_SCROLLCURSOR);
219         else
220                 WM_cursor_modal(window, BC_NSEW_SCROLLCURSOR);
221         
222         /* add temp handler */
223         WM_event_add_modal_handler(C, op);
224
225         return OPERATOR_RUNNING_MODAL;
226 }
227
228 /* handle user input - calculations of mouse-movement need to be done here, not in the apply callback! */
229 static int view_pan_modal(bContext *C, wmOperator *op, wmEvent *event)
230 {
231         v2dViewPanData *vpd= op->customdata;
232         
233         /* execute the events */
234         switch (event->type) {
235                 case MOUSEMOVE:
236                 {
237                         /* calculate new delta transform, then store mouse-coordinates for next-time */
238                         RNA_int_set(op->ptr, "deltax", (vpd->lastx - event->x));
239                         RNA_int_set(op->ptr, "deltay", (vpd->lasty - event->y));
240                         
241                         vpd->lastx= event->x;
242                         vpd->lasty= event->y;
243                         
244                         view_pan_apply(C, op);
245                 }
246                         break;
247                         
248                 case LEFTMOUSE:
249                         /* switch to zoom */
250                         if (event->val==KM_PRESS) {
251                                 /* calculate overall delta mouse-movement for redo */
252                                 RNA_int_set(op->ptr, "deltax", (vpd->startx - vpd->lastx));
253                                 RNA_int_set(op->ptr, "deltay", (vpd->starty - vpd->lasty));
254                                 
255                                 view_pan_exit(C, op);
256                                 WM_cursor_restore(CTX_wm_window(C));
257                                 
258                                 WM_operator_name_call(C, "VIEW2D_OT_zoom", WM_OP_INVOKE_DEFAULT, NULL);
259                                 return OPERATOR_FINISHED;
260                         }
261                 case MIDDLEMOUSE:
262                 case ESCKEY:
263                         if (event->val==KM_RELEASE) {
264                                 /* calculate overall delta mouse-movement for redo */
265                                 RNA_int_set(op->ptr, "deltax", (vpd->startx - vpd->lastx));
266                                 RNA_int_set(op->ptr, "deltay", (vpd->starty - vpd->lasty));
267                                 
268                                 view_pan_exit(C, op);
269                                 WM_cursor_restore(CTX_wm_window(C));
270                                 
271                                 return OPERATOR_FINISHED;
272                         }
273                         break;
274         }
275
276         return OPERATOR_RUNNING_MODAL;
277 }
278
279 static int view_pan_cancel(bContext *C, wmOperator *op)
280 {
281         view_pan_exit(C, op);
282         return OPERATOR_CANCELLED;
283 }
284
285 void VIEW2D_OT_pan(wmOperatorType *ot)
286 {
287         /* identifiers */
288         ot->name= "Pan View";
289         ot->description= "Pan the view.";
290         ot->idname= "VIEW2D_OT_pan";
291         
292         /* api callbacks */
293         ot->exec= view_pan_exec;
294         ot->invoke= view_pan_invoke;
295         ot->modal= view_pan_modal;
296         ot->cancel= view_pan_cancel;
297         
298         /* operator is repeatable */
299         ot->flag= OPTYPE_BLOCKING|OPTYPE_GRAB_POINTER;
300         
301         /* rna - must keep these in sync with the other operators */
302         RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX);
303         RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX);
304 }
305
306 /* ------------------ Scrollwheel Versions (2) ---------------------- */
307
308 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
309 static int view_scrollright_exec(bContext *C, wmOperator *op)
310 {
311         v2dViewPanData *vpd;
312         
313         /* initialise default settings (and validate if ok to run) */
314         if (!view_pan_init(C, op))
315                 return OPERATOR_PASS_THROUGH;
316                 
317         /* also, check if can pan in horizontal axis */
318         vpd= op->customdata;
319         if (vpd->v2d->keepofs & V2D_LOCKOFS_X) {
320                 view_pan_exit(C, op);
321                 return OPERATOR_PASS_THROUGH;
322         }
323         
324         /* set RNA-Props - only movement in positive x-direction */
325         RNA_int_set(op->ptr, "deltax", 20);
326         RNA_int_set(op->ptr, "deltay", 0);
327         
328         /* apply movement, then we're done */
329         view_pan_apply(C, op);
330         view_pan_exit(C, op);
331         
332         return OPERATOR_FINISHED;
333 }
334
335 void VIEW2D_OT_scroll_right(wmOperatorType *ot)
336 {
337         /* identifiers */
338         ot->name= "Scroll Right";
339         ot->description= "Scroll the view right.";
340         ot->idname= "VIEW2D_OT_scroll_right";
341         
342         /* api callbacks */
343         ot->exec= view_scrollright_exec;
344         
345         /* operator is repeatable */
346         // ot->flag= OPTYPE_REGISTER;
347         
348         /* rna - must keep these in sync with the other operators */
349         RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX);
350         RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX);
351 }
352
353
354
355 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
356 static int view_scrollleft_exec(bContext *C, wmOperator *op)
357 {
358         v2dViewPanData *vpd;
359         
360         /* initialise default settings (and validate if ok to run) */
361         if (!view_pan_init(C, op))
362                 return OPERATOR_PASS_THROUGH;
363                 
364         /* also, check if can pan in horizontal axis */
365         vpd= op->customdata;
366         if (vpd->v2d->keepofs & V2D_LOCKOFS_X) {
367                 view_pan_exit(C, op);
368                 return OPERATOR_PASS_THROUGH;
369         }
370         
371         /* set RNA-Props - only movement in negative x-direction */
372         RNA_int_set(op->ptr, "deltax", -20);
373         RNA_int_set(op->ptr, "deltay", 0);
374         
375         /* apply movement, then we're done */
376         view_pan_apply(C, op);
377         view_pan_exit(C, op);
378         
379         return OPERATOR_FINISHED;
380 }
381
382 void VIEW2D_OT_scroll_left(wmOperatorType *ot)
383 {
384         /* identifiers */
385         ot->name= "Scroll Left";
386         ot->description= "Scroll the view left.";
387         ot->idname= "VIEW2D_OT_scroll_left";
388         
389         /* api callbacks */
390         ot->exec= view_scrollleft_exec;
391         
392         /* operator is repeatable */
393         // ot->flag= OPTYPE_REGISTER;
394         
395         /* rna - must keep these in sync with the other operators */
396         RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX);
397         RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX);
398 }
399
400
401 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
402 static int view_scrolldown_exec(bContext *C, wmOperator *op)
403 {
404         v2dViewPanData *vpd;
405         
406         /* initialise default settings (and validate if ok to run) */
407         if (!view_pan_init(C, op))
408                 return OPERATOR_PASS_THROUGH;
409                 
410         /* also, check if can pan in vertical axis */
411         vpd= op->customdata;
412         if (vpd->v2d->keepofs & V2D_LOCKOFS_Y) {
413                 view_pan_exit(C, op);
414                 return OPERATOR_PASS_THROUGH;
415         }
416         
417         /* set RNA-Props */
418         RNA_int_set(op->ptr, "deltax", 0);
419         RNA_int_set(op->ptr, "deltay", -20);
420         
421         /* apply movement, then we're done */
422         view_pan_apply(C, op);
423         view_pan_exit(C, op);
424         
425         return OPERATOR_FINISHED;
426 }
427
428 void VIEW2D_OT_scroll_down(wmOperatorType *ot)
429 {
430         /* identifiers */
431         ot->name= "Scroll Down";
432         ot->description= "Scroll the view down.";
433         ot->idname= "VIEW2D_OT_scroll_down";
434         
435         /* api callbacks */
436         ot->exec= view_scrolldown_exec;
437         
438         /* operator is repeatable */
439         // ot->flag= OPTYPE_REGISTER;
440         
441         /* rna - must keep these in sync with the other operators */
442         RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX);
443         RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX);
444 }
445
446
447
448 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
449 static int view_scrollup_exec(bContext *C, wmOperator *op)
450 {
451         v2dViewPanData *vpd;
452         
453         /* initialise default settings (and validate if ok to run) */
454         if (!view_pan_init(C, op))
455                 return OPERATOR_PASS_THROUGH;
456                 
457         /* also, check if can pan in vertical axis */
458         vpd= op->customdata;
459         if (vpd->v2d->keepofs & V2D_LOCKOFS_Y) {
460                 view_pan_exit(C, op);
461                 return OPERATOR_PASS_THROUGH;
462         }
463         
464         /* set RNA-Props */
465         RNA_int_set(op->ptr, "deltax", 0);
466         RNA_int_set(op->ptr, "deltay", 20);
467         
468         /* apply movement, then we're done */
469         view_pan_apply(C, op);
470         view_pan_exit(C, op);
471         
472         return OPERATOR_FINISHED;
473 }
474
475 void VIEW2D_OT_scroll_up(wmOperatorType *ot)
476 {
477         /* identifiers */
478         ot->name= "Scroll Up";
479         ot->description= "Scroll the view up.";
480         ot->idname= "VIEW2D_OT_scroll_up";
481         
482         /* api callbacks */
483         ot->exec= view_scrollup_exec;
484         
485         /* operator is repeatable */
486         // ot->flag= OPTYPE_REGISTER;
487         
488         /* rna - must keep these in sync with the other operators */
489         RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX);
490         RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX);
491 }
492
493 /* ********************************************************* */
494 /* SINGLE-STEP VIEW ZOOMING OPERATOR                                             */
495
496 /*      This group of operators come in several forms:
497  *              1) Scrollwheel 'steps' - rolling mousewheel by one step zooms view by predefined amount
498  *              2) Scrollwheel 'steps' + alt + ctrl/shift - zooms view on one axis only (ctrl=x, shift=y)  // XXX this could be implemented...
499  *              3) Pad +/- Keys - pressing each key moves the zooms the view by a predefined amount
500  *
501  *      In order to make sure this works, each operator must define the following RNA-Operator Props:
502  *              zoomfacx, zoomfacy      - These two zoom factors allow for non-uniform scaling.
503  *                                                        It is safe to scale by 0, as these factors are used to determine
504  *                                                        amount to enlarge 'cur' by
505  */
506
507 /* ------------------ 'Shared' stuff ------------------------ */
508
509 /* temp customdata for operator */
510 typedef struct v2dViewZoomData {
511         View2D *v2d;                    /* view2d we're operating in */
512         
513         int lastx, lasty;               /* previous x/y values of mouse in window */
514         float dx, dy;                   /* running tally of previous delta values (for obtaining final zoom) */
515         float mx_2d, my_2d;             /* initial mouse location in v2d coords */
516 } v2dViewZoomData;
517
518
519 /* initialise panning customdata */
520 static int view_zoomdrag_init(bContext *C, wmOperator *op)
521 {
522         ARegion *ar= CTX_wm_region(C);
523         v2dViewZoomData *vzd;
524         View2D *v2d;
525         
526         /* regions now have v2d-data by default, so check for region */
527         if (ar == NULL)
528                 return 0;
529         v2d= &ar->v2d;
530         
531         /* check that 2d-view is zoomable */
532         if ((v2d->keepzoom & V2D_LOCKZOOM_X) && (v2d->keepzoom & V2D_LOCKZOOM_Y))
533                 return 0;
534         
535         /* set custom-data for operator */
536         vzd= MEM_callocN(sizeof(v2dViewZoomData), "v2dViewZoomData");
537         op->customdata= vzd;
538         
539         /* set pointers to owners */
540         vzd->v2d= v2d;
541         
542         return 1;
543 }
544
545 /* check if step-zoom can be applied */
546 static int view_zoom_poll(bContext *C)
547 {
548         ARegion *ar= CTX_wm_region(C);
549         View2D *v2d;
550         
551         /* check if there's a region in context to work with */
552         if (ar == NULL)
553                 return 0;
554         v2d= &ar->v2d;
555         
556         /* check that 2d-view is zoomable */
557         if ((v2d->keepzoom & V2D_LOCKZOOM_X) && (v2d->keepzoom & V2D_LOCKZOOM_Y))
558                 return 0;
559                 
560         /* view is zoomable */
561         return 1;
562 }
563  
564 /* apply transform to view (i.e. adjust 'cur' rect) */
565 static void view_zoomstep_apply(bContext *C, wmOperator *op)
566 {
567         v2dViewZoomData *vzd= op->customdata;
568         ARegion *ar= CTX_wm_region(C);
569         View2D *v2d= &ar->v2d;
570         float dx, dy, facx, facy;
571         
572         /* calculate amount to move view by, ensuring symmetry so the
573          * old zoom level is restored after zooming back the same amount */
574         facx= RNA_float_get(op->ptr, "zoomfacx");
575         facy= RNA_float_get(op->ptr, "zoomfacy");
576
577         if(facx >= 0.0f) {
578                 dx= (v2d->cur.xmax - v2d->cur.xmin) * facx;
579                 dy= (v2d->cur.ymax - v2d->cur.ymin) * facy;
580         }
581         else {
582                 dx= ((v2d->cur.xmax - v2d->cur.xmin)/(1.0f + 2.0f*facx)) * facx;
583                 dy= ((v2d->cur.ymax - v2d->cur.ymin)/(1.0f + 2.0f*facy)) * facy;
584         }
585
586         /* only resize view on an axis if change is allowed */
587         if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0) {
588                 if (v2d->keepofs & V2D_LOCKOFS_X) {
589                         v2d->cur.xmax -= 2*dx;
590                 }
591                 else if (v2d->keepofs & V2D_KEEPOFS_X) {
592                         if(v2d->align & V2D_ALIGN_NO_POS_X)
593                                 v2d->cur.xmin += 2*dx;
594                         else
595                                 v2d->cur.xmax -= 2*dx;
596                 }
597                 else {
598                         if(U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
599                                 float mval_fac = (vzd->mx_2d - v2d->cur.xmin) / (v2d->cur.xmax-v2d->cur.xmin);
600                                 float mval_faci = 1.0 - mval_fac;
601                                 float ofs= (mval_fac * dx) - (mval_faci * dx);
602                                 v2d->cur.xmin += ofs + dx;
603                                 v2d->cur.xmax += ofs - dx;
604                         }
605                         else {
606                                 v2d->cur.xmin += dx;
607                                 v2d->cur.xmax -= dx;
608                         }
609                 }
610         }
611         if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0) {
612                 if (v2d->keepofs & V2D_LOCKOFS_Y) {
613                         v2d->cur.ymax -= 2*dy;
614                 }
615                 else if (v2d->keepofs & V2D_KEEPOFS_Y) {
616                         if(v2d->align & V2D_ALIGN_NO_POS_Y)
617                                 v2d->cur.ymin += 2*dy;
618                         else
619                                 v2d->cur.ymax -= 2*dy;
620                 }
621                 else {
622                         if(U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
623                                 float mval_fac = (vzd->my_2d - v2d->cur.ymin) / (v2d->cur.ymax-v2d->cur.ymin);
624                                 float mval_faci = 1.0 - mval_fac;
625                                 float ofs= (mval_fac * dy) - (mval_faci * dy);
626                                 v2d->cur.ymin += ofs + dy;
627                                 v2d->cur.ymax += ofs - dy;
628                         } else {
629                                 v2d->cur.ymin += dy;
630                                 v2d->cur.ymax -= dy;
631                         }
632                 }
633         }
634
635         /* validate that view is in valid configuration after this operation */
636         UI_view2d_curRect_validate(v2d);
637
638         /* request updates to be done... */
639         ED_area_tag_redraw(CTX_wm_area(C));
640         UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
641         WM_event_add_mousemove(C);
642 }
643
644 /* --------------- Individual Operators ------------------- */
645
646 /* cleanup temp customdata  */
647 static void view_zoomstep_exit(bContext *C, wmOperator *op)
648 {
649         if (op->customdata) {
650                 MEM_freeN(op->customdata);
651                 op->customdata= NULL;                           
652         }
653 }
654
655 /* this operator only needs this single callback, where it calls the view_zoom_*() methods */
656 static int view_zoomin_exec(bContext *C, wmOperator *op)
657 {
658         /* check that there's an active region, as View2D data resides there */
659         if (!view_zoom_poll(C))
660                 return OPERATOR_PASS_THROUGH;
661         
662         /* set RNA-Props - zooming in by uniform factor */
663         RNA_float_set(op->ptr, "zoomfacx", 0.0375f);
664         RNA_float_set(op->ptr, "zoomfacy", 0.0375f);
665         
666         /* apply movement, then we're done */
667         view_zoomstep_apply(C, op);
668         
669         view_zoomstep_exit(C, op);
670         
671         return OPERATOR_FINISHED;
672 }
673
674 static int view_zoomin_invoke(bContext *C, wmOperator *op, wmEvent *event)
675 {
676         v2dViewZoomData *vzd;
677         
678         if (!view_zoomdrag_init(C, op))
679                 return OPERATOR_PASS_THROUGH;
680         
681         vzd= op->customdata;
682         
683         if(U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
684                 ARegion *ar= CTX_wm_region(C);
685                 UI_view2d_region_to_view(&ar->v2d, event->x - ar->winrct.xmin, event->y - ar->winrct.ymin, &vzd->mx_2d, &vzd->my_2d);
686         }
687         
688         return view_zoomin_exec(C, op);
689 }
690
691 void VIEW2D_OT_zoom_in(wmOperatorType *ot)
692 {
693         /* identifiers */
694         ot->name= "Zoom In";
695         ot->description= "Zoom in the view.";
696         ot->idname= "VIEW2D_OT_zoom_in";
697         
698         /* api callbacks */
699         ot->invoke= view_zoomin_invoke;
700         ot->exec= view_zoomin_exec;
701         ot->poll= view_zoom_poll;
702         
703         /* operator is repeatable */
704         // ot->flag= OPTYPE_REGISTER;
705         
706         /* rna - must keep these in sync with the other operators */
707         RNA_def_float(ot->srna, "zoomfacx", 0, -FLT_MAX, FLT_MAX, "Zoom Factor X", "", -FLT_MAX, FLT_MAX);
708         RNA_def_float(ot->srna, "zoomfacy", 0, -FLT_MAX, FLT_MAX, "Zoom Factor Y", "", -FLT_MAX, FLT_MAX);
709 }
710         
711 /* this operator only needs this single callback, where it callsthe view_zoom_*() methods */
712 static int view_zoomout_exec(bContext *C, wmOperator *op)
713 {
714         /* check that there's an active region, as View2D data resides there */
715         if (!view_zoom_poll(C))
716                 return OPERATOR_PASS_THROUGH;
717         
718         /* set RNA-Props - zooming in by uniform factor */
719         RNA_float_set(op->ptr, "zoomfacx", -0.0375f);
720         RNA_float_set(op->ptr, "zoomfacy", -0.0375f);
721         
722         /* apply movement, then we're done */
723         view_zoomstep_apply(C, op);
724
725         view_zoomstep_exit(C, op);
726         
727         return OPERATOR_FINISHED;
728 }
729
730 static int view_zoomout_invoke(bContext *C, wmOperator *op, wmEvent *event)
731 {
732         v2dViewZoomData *vzd;
733         
734         if (!view_zoomdrag_init(C, op))
735                 return OPERATOR_PASS_THROUGH;
736
737         vzd= op->customdata;
738         
739         if(U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
740                 ARegion *ar= CTX_wm_region(C);
741                 UI_view2d_region_to_view(&ar->v2d, event->x - ar->winrct.xmin, event->y - ar->winrct.ymin, &vzd->mx_2d, &vzd->my_2d);
742         }
743         
744         return view_zoomout_exec(C, op);
745 }
746
747 void VIEW2D_OT_zoom_out(wmOperatorType *ot)
748 {
749         /* identifiers */
750         ot->name= "Zoom Out";
751         ot->description= "Zoom out the view.";
752         ot->idname= "VIEW2D_OT_zoom_out";
753         
754         /* api callbacks */
755         ot->invoke= view_zoomout_invoke;
756         ot->exec= view_zoomout_exec;
757         ot->poll= view_zoom_poll;
758         
759         /* operator is repeatable */
760         // ot->flag= OPTYPE_REGISTER;
761         
762         /* rna - must keep these in sync with the other operators */
763         RNA_def_float(ot->srna, "zoomfacx", 0, -FLT_MAX, FLT_MAX, "Zoom Factor X", "", -FLT_MAX, FLT_MAX);
764         RNA_def_float(ot->srna, "zoomfacy", 0, -FLT_MAX, FLT_MAX, "Zoom Factor Y", "", -FLT_MAX, FLT_MAX);
765 }
766
767 /* ********************************************************* */
768 /* DRAG-ZOOM OPERATOR                                                                    */
769
770 /*      MMB Drag - allows non-uniform scaling by dragging mouse
771  *
772  *      In order to make sure this works, each operator must define the following RNA-Operator Props:
773  *              deltax, deltay  - amounts to add to each side of the 'cur' rect
774  */
775
776 /* apply transform to view (i.e. adjust 'cur' rect) */
777 static void view_zoomdrag_apply(bContext *C, wmOperator *op)
778 {
779         v2dViewZoomData *vzd= op->customdata;
780         View2D *v2d= vzd->v2d;
781         float dx, dy;
782         
783         /* get amount to move view by */
784         dx= RNA_float_get(op->ptr, "deltax");
785         dy= RNA_float_get(op->ptr, "deltay");
786         
787         /* only move view on an axis if change is allowed */
788         if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0) {
789                 if (v2d->keepofs & V2D_LOCKOFS_X) {
790                         v2d->cur.xmax -= 2*dx;
791                 }
792                 else {
793                         if(U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
794                                 float mval_fac = (vzd->mx_2d - v2d->cur.xmin) / (v2d->cur.xmax-v2d->cur.xmin);
795                                 float mval_faci = 1.0 - mval_fac;
796                                 float ofs= (mval_fac * dx) - (mval_faci * dx);
797                                 v2d->cur.xmin += ofs + dx;
798                                 v2d->cur.xmax += ofs - dx;
799                         }
800                         else {
801                                 v2d->cur.xmin += dx;
802                                 v2d->cur.xmax -= dx;
803                         }
804                 }
805         }
806         if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0) {
807                 if (v2d->keepofs & V2D_LOCKOFS_Y) {
808                         v2d->cur.ymax -= 2*dy;
809                 }
810                 else {
811                         if(U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
812                                 float mval_fac = (vzd->my_2d - v2d->cur.ymin) / (v2d->cur.ymax-v2d->cur.ymin);
813                                 float mval_faci = 1.0 - mval_fac;
814                                 float ofs= (mval_fac * dy) - (mval_faci * dy);
815                                 v2d->cur.ymin += ofs + dy;
816                                 v2d->cur.ymax += ofs - dy;
817                         }
818                         else {
819                                 v2d->cur.ymin += dy;
820                                 v2d->cur.ymax -= dy;
821                         }
822                 }
823         }
824         
825         /* validate that view is in valid configuration after this operation */
826         UI_view2d_curRect_validate(v2d);
827         
828         /* request updates to be done... */
829         ED_area_tag_redraw(CTX_wm_area(C));
830         UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
831         WM_event_add_mousemove(C);
832 }
833
834 /* cleanup temp customdata  */
835 static void view_zoomdrag_exit(bContext *C, wmOperator *op)
836 {
837         if (op->customdata) {
838                 MEM_freeN(op->customdata);
839                 op->customdata= NULL;                           
840         }
841
842
843 /* for 'redo' only, with no user input */
844 static int view_zoomdrag_exec(bContext *C, wmOperator *op)
845 {
846         if (!view_zoomdrag_init(C, op))
847                 return OPERATOR_PASS_THROUGH;
848         
849         view_zoomdrag_apply(C, op);
850         view_zoomdrag_exit(C, op);
851         return OPERATOR_FINISHED;
852 }
853
854 /* set up modal operator and relevant settings */
855 static int view_zoomdrag_invoke(bContext *C, wmOperator *op, wmEvent *event)
856 {
857         wmWindow *window= CTX_wm_window(C);
858         v2dViewZoomData *vzd;
859         View2D *v2d;
860         
861         /* set up customdata */
862         if (!view_zoomdrag_init(C, op))
863                 return OPERATOR_PASS_THROUGH;
864         
865         vzd= op->customdata;
866         v2d= vzd->v2d;
867         
868         if (event->type == MOUSEZOOM) {
869                 float dx, dy, fac;
870                 
871                 vzd->lastx= event->prevx;
872                 vzd->lasty= event->prevy;
873                 
874                 /* As we have only 1D information (magnify value), feed both axes
875                 with magnify information that is stored in x axis */
876                 fac= 0.01f * (event->x - event->prevx);
877                 dx= fac * (v2d->cur.xmax - v2d->cur.xmin) / 10.0f;
878                 dy= fac * (v2d->cur.ymax - v2d->cur.ymin) / 10.0f;
879
880                 RNA_float_set(op->ptr, "deltax", dx);
881                 RNA_float_set(op->ptr, "deltay", dy);
882                 
883                 view_zoomdrag_apply(C, op);
884                 view_zoomdrag_exit(C, op);
885                 return OPERATOR_FINISHED;
886         }       
887         
888         /* set initial settings */
889         vzd->lastx= event->x;
890         vzd->lasty= event->y;
891         RNA_float_set(op->ptr, "deltax", 0);
892         RNA_float_set(op->ptr, "deltay", 0);
893         
894         if(U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
895                 ARegion *ar= CTX_wm_region(C);
896                 UI_view2d_region_to_view(&ar->v2d, event->x - ar->winrct.xmin, event->y - ar->winrct.ymin, &vzd->mx_2d, &vzd->my_2d);
897         }
898
899         if (v2d->keepofs & V2D_LOCKOFS_X)
900                 WM_cursor_modal(window, BC_NS_SCROLLCURSOR);
901         else if (v2d->keepofs & V2D_LOCKOFS_Y)
902                 WM_cursor_modal(window, BC_EW_SCROLLCURSOR);
903         else
904                 WM_cursor_modal(window, BC_NSEW_SCROLLCURSOR);
905         
906         /* add temp handler */
907         WM_event_add_modal_handler(C, op);
908
909         return OPERATOR_RUNNING_MODAL;
910 }
911
912 /* handle user input - calculations of mouse-movement need to be done here, not in the apply callback! */
913 static int view_zoomdrag_modal(bContext *C, wmOperator *op, wmEvent *event)
914 {
915         v2dViewZoomData *vzd= op->customdata;
916         View2D *v2d= vzd->v2d;
917         
918         /* execute the events */
919         switch (event->type) {
920                 case MOUSEMOVE:
921                 {
922                         float dx, dy;
923                         
924                         /* calculate new delta transform, based on zooming mode */
925                         if (U.viewzoom == USER_ZOOM_SCALE) {
926                                 /* 'scale' zooming */
927                                 float dist;
928                                 
929                                 /* x-axis transform */
930                                 dist = (v2d->mask.xmax - v2d->mask.xmin) / 2.0f;
931                                 dx= 1.0f - ((float)fabs(vzd->lastx - dist) + 2.0f) / ((float)fabs(event->x - dist) + 2.0f);
932                                 dx*= 0.5f * (v2d->cur.xmax - v2d->cur.xmin);
933                                 
934                                 /* y-axis transform */
935                                 dist = (v2d->mask.ymax - v2d->mask.ymin) / 2.0f;
936                                 dy= 1.0f - ((float)fabs(vzd->lasty - dist) + 2.0f) / ((float)fabs(event->y - dist) + 2.0f);
937                                 dy*= 0.5f * (v2d->cur.ymax - v2d->cur.ymin);
938                         }
939                         else {
940                                 /* 'continuous' or 'dolly' */
941                                 float fac;
942                                 
943                                 /* x-axis transform */
944                                 fac= 0.01f * (event->x - vzd->lastx);
945                                 dx= fac * (v2d->cur.xmax - v2d->cur.xmin);
946                                 
947                                 /* y-axis transform */
948                                 fac= 0.01f * (event->y - vzd->lasty);
949                                 dy= fac * (v2d->cur.ymax - v2d->cur.ymin);
950                                 
951                                 /* continous zoom shouldn't move that fast... */
952                                 if (U.viewzoom == USER_ZOOM_CONT) { // XXX store this setting as RNA prop?
953                                         dx /= 20.0f;
954                                         dy /= 20.0f;
955                                 }
956                         }
957                         
958                         /* set transform amount, and add current deltas to stored total delta (for redo) */
959                         RNA_float_set(op->ptr, "deltax", dx);
960                         RNA_float_set(op->ptr, "deltay", dy);
961                         vzd->dx += dx;
962                         vzd->dy += dy;
963                         
964                         /* store mouse coordinates for next time, if not doing continuous zoom
965                          *      - continuous zoom only depends on distance of mouse to starting point to determine rate of change
966                          */
967                         if (U.viewzoom != USER_ZOOM_CONT) { // XXX store this setting as RNA prop?
968                                 vzd->lastx= event->x;
969                                 vzd->lasty= event->y;
970                         }
971                         
972                         /* apply zooming */
973                         view_zoomdrag_apply(C, op);
974                 }
975                         break;
976                         
977                 case LEFTMOUSE:
978                 case MIDDLEMOUSE:
979                         if (event->val==KM_RELEASE) {
980                                 /* for redo, store the overall deltas - need to respect zoom-locks here... */
981                                 if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0)
982                                         RNA_float_set(op->ptr, "deltax", vzd->dx);
983                                 else
984                                         RNA_float_set(op->ptr, "deltax", 0);
985                                         
986                                 if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0)
987                                         RNA_float_set(op->ptr, "deltay", vzd->dy);
988                                 else
989                                         RNA_float_set(op->ptr, "deltay", 0);
990                                 
991                                 /* free customdata */
992                                 view_zoomdrag_exit(C, op);
993                                 WM_cursor_restore(CTX_wm_window(C));
994                                 
995                                 return OPERATOR_FINISHED;
996                         }
997                         break;
998         }
999
1000         return OPERATOR_RUNNING_MODAL;
1001 }
1002
1003 void VIEW2D_OT_zoom(wmOperatorType *ot)
1004 {
1005         /* identifiers */
1006         ot->name= "Zoom View";
1007         ot->description= "Zoom in/out the view.";
1008         ot->idname= "VIEW2D_OT_zoom";
1009         
1010         /* api callbacks */
1011         ot->exec= view_zoomdrag_exec;
1012         ot->invoke= view_zoomdrag_invoke;
1013         ot->modal= view_zoomdrag_modal;
1014         
1015         ot->poll= view_zoom_poll;
1016         
1017         /* operator is repeatable */
1018         // ot->flag= OPTYPE_REGISTER|OPTYPE_BLOCKING;
1019         
1020         /* rna - must keep these in sync with the other operators */
1021         RNA_def_float(ot->srna, "deltax", 0, -FLT_MAX, FLT_MAX, "Delta X", "", -FLT_MAX, FLT_MAX);
1022         RNA_def_float(ot->srna, "deltay", 0, -FLT_MAX, FLT_MAX, "Delta Y", "", -FLT_MAX, FLT_MAX);
1023 }
1024
1025 /* ********************************************************* */
1026 /* BORDER-ZOOM */
1027
1028 /* The user defines a rect using standard borderselect tools, and we use this rect to 
1029  * define the new zoom-level of the view in the following ways:
1030  *      1) LEFTMOUSE - zoom in to view
1031  *      2) RIGHTMOUSE - zoom out of view
1032  *
1033  * Currently, these key mappings are hardcoded, but it shouldn't be too important to
1034  * have custom keymappings for this...
1035  */
1036  
1037 static int view_borderzoom_exec(bContext *C, wmOperator *op)
1038 {
1039         ARegion *ar= CTX_wm_region(C);
1040         View2D *v2d= &ar->v2d;
1041         rctf rect;
1042         int event_type;
1043         
1044         /* convert coordinates of rect to 'tot' rect coordinates */
1045         UI_view2d_region_to_view(v2d, RNA_int_get(op->ptr, "xmin"), RNA_int_get(op->ptr, "ymin"), &rect.xmin, &rect.ymin);
1046         UI_view2d_region_to_view(v2d, RNA_int_get(op->ptr, "xmax"), RNA_int_get(op->ptr, "ymax"), &rect.xmax, &rect.ymax);
1047         
1048         /* check if zooming in/out view */
1049         // XXX hardcoded for now!
1050         event_type= RNA_int_get(op->ptr, "event_type");
1051         
1052         if (event_type == LEFTMOUSE) {
1053                 /* zoom in: 
1054                  *      - 'cur' rect will be defined by the coordinates of the border region 
1055                  *      - just set the 'cur' rect to have the same coordinates as the border region
1056                  *        if zoom is allowed to be changed
1057                  */
1058                 if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0) {
1059                         v2d->cur.xmin= rect.xmin;
1060                         v2d->cur.xmax= rect.xmax;
1061                 }
1062                 if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0) {
1063                         v2d->cur.ymin= rect.ymin;
1064                         v2d->cur.ymax= rect.ymax;
1065                 }
1066         }
1067         else {
1068                 /* zoom out:
1069                  *      - the current 'cur' rect coordinates are going to end upwhere the 'rect' ones are, 
1070                  *        but the 'cur' rect coordinates will need to be adjusted to take in more of the view
1071                  *      - calculate zoom factor, and adjust using center-point
1072                  */
1073                 float zoom, center, size;
1074                 
1075                 // TODO: is this zoom factor calculation valid? It seems to produce same results everytime...
1076                 if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0) {
1077                         size= (v2d->cur.xmax - v2d->cur.xmin);
1078                         zoom= size / (rect.xmax - rect.xmin);
1079                         center= (v2d->cur.xmax + v2d->cur.xmin) * 0.5f;
1080                         
1081                         v2d->cur.xmin= center - (size * zoom);
1082                         v2d->cur.xmax= center + (size * zoom);
1083                 }
1084                 if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0) {
1085                         size= (v2d->cur.ymax - v2d->cur.ymin);
1086                         zoom= size / (rect.ymax - rect.ymin);
1087                         center= (v2d->cur.ymax + v2d->cur.ymin) * 0.5f;
1088                         
1089                         v2d->cur.ymin= center - (size * zoom);
1090                         v2d->cur.ymax= center + (size * zoom);
1091                 }
1092         }
1093         
1094         /* validate that view is in valid configuration after this operation */
1095         UI_view2d_curRect_validate(v2d);
1096         
1097         /* request updates to be done... */
1098         ED_area_tag_redraw(CTX_wm_area(C));
1099         UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
1100         WM_event_add_mousemove(C);
1101         
1102         return OPERATOR_FINISHED;
1103
1104
1105 void VIEW2D_OT_zoom_border(wmOperatorType *ot)
1106 {
1107         /* identifiers */
1108         ot->name= "Zoom to Border";
1109         ot->description= "Zoom in the view to the nearest item contained in the border.";
1110         ot->idname= "VIEW2D_OT_zoom_border";
1111         
1112         /* api callbacks */
1113         ot->invoke= WM_border_select_invoke;
1114         ot->exec= view_borderzoom_exec;
1115         ot->modal= WM_border_select_modal;
1116         
1117         ot->poll= view_zoom_poll;
1118         
1119         /* rna */
1120         RNA_def_int(ot->srna, "event_type", 0, INT_MIN, INT_MAX, "Event Type", "", INT_MIN, INT_MAX);
1121         RNA_def_int(ot->srna, "xmin", 0, INT_MIN, INT_MAX, "X Min", "", INT_MIN, INT_MAX);
1122         RNA_def_int(ot->srna, "xmax", 0, INT_MIN, INT_MAX, "X Max", "", INT_MIN, INT_MAX);
1123         RNA_def_int(ot->srna, "ymin", 0, INT_MIN, INT_MAX, "Y Min", "", INT_MIN, INT_MAX);
1124         RNA_def_int(ot->srna, "ymax", 0, INT_MIN, INT_MAX, "Y Max", "", INT_MIN, INT_MAX);
1125 }
1126
1127 /* ********************************************************* */
1128 /* SCROLLERS */
1129
1130 /*      Scrollers should behave in the following ways, when clicked on with LMB (and dragged):
1131  *              1) 'Handles' on end of 'bubble' - when the axis that the scroller represents is zoomable, 
1132  *                      enlarge 'cur' rect on the relevant side 
1133  *              2) 'Bubble'/'bar' - just drag, and bar should move with mouse (view pans opposite)
1134  *
1135  *      In order to make sure this works, each operator must define the following RNA-Operator Props:
1136  *              deltax, deltay  - define how much to move view by (relative to zoom-correction factor)
1137  */
1138
1139 /* customdata for scroller-invoke data */
1140 typedef struct v2dScrollerMove {
1141         View2D *v2d;                    /* View2D data that this operation affects */
1142         
1143         short scroller;                 /* scroller that mouse is in ('h' or 'v') */
1144         short zone;                             /* -1 is min zoomer, 0 is bar, 1 is max zoomer */ // XXX find some way to provide visual feedback of this (active colour?)
1145         
1146         float fac;                              /* view adjustment factor, based on size of region */
1147         float delta;                    /* amount moved by mouse on axis of interest */
1148         
1149         int lastx, lasty;               /* previous mouse coordinates (in screen coordinates) for determining movement */
1150 } v2dScrollerMove;
1151
1152
1153 /* View2DScrollers is typedef'd in UI_view2d.h 
1154  * This is a CUT DOWN VERSION of the 'real' version, which is defined in view2d.c, as we only need focus bubble info
1155  * WARNING: the start of this struct must not change, so that it stays in sync with the 'real' version
1156  *                 For now, we don't need to have a separate (internal) header for structs like this...
1157  */
1158 struct View2DScrollers {        
1159                 /* focus bubbles */
1160         int vert_min, vert_max; /* vertical scrollbar */
1161         int hor_min, hor_max;   /* horizontal scrollbar */
1162 };
1163
1164 /* quick enum for vsm->zone (scroller handles) */
1165 enum {
1166         SCROLLHANDLE_MIN= -1,
1167         SCROLLHANDLE_BAR,
1168         SCROLLHANDLE_MAX
1169 } eV2DScrollerHandle_Zone;
1170
1171 /* ------------------------ */
1172
1173 /* check if mouse is within scroller handle 
1174  *      - mouse                 =       relevant mouse coordinate in region space
1175  *      - sc_min, sc_max        =       extents of scroller
1176  *      - sh_min, sh_max        =       positions of scroller handles
1177  */
1178 static short mouse_in_scroller_handle(int mouse, int sc_min, int sc_max, int sh_min, int sh_max)
1179 {
1180         short in_min, in_max, in_view=1;
1181         
1182         /* firstly, check if 
1183          *      - 'bubble' fills entire scroller 
1184          *      - 'bubble' completely out of view on either side 
1185          */
1186         if ((sh_min <= sc_min) && (sh_max >= sc_max)) in_view= 0;
1187         if (sh_min == sh_max) {
1188                 if (sh_min <= sc_min) in_view= 0;
1189                 if (sh_max >= sc_max) in_view= 0;
1190         }
1191         else {
1192                 if (sh_max <= sc_min) in_view= 0;
1193                 if (sh_min >= sc_max) in_view= 0;
1194         }
1195         
1196         
1197         if (in_view == 0) {
1198                 return SCROLLHANDLE_BAR;
1199         }
1200         
1201         /* check if mouse is in or past either handle */
1202         in_max= ( (mouse >= (sh_max - V2D_SCROLLER_HANDLE_SIZE)) && (mouse <= (sh_max + V2D_SCROLLER_HANDLE_SIZE)) );
1203         in_min= ( (mouse <= (sh_min + V2D_SCROLLER_HANDLE_SIZE)) && (mouse >= (sh_min - V2D_SCROLLER_HANDLE_SIZE)) );
1204         
1205         /* check if overlap --> which means user clicked on bar, as bar is within handles region */
1206         if (in_max && in_min)
1207                 return SCROLLHANDLE_BAR;
1208         else if (in_max)
1209                 return SCROLLHANDLE_MAX;
1210         else if (in_min)
1211                 return SCROLLHANDLE_MIN;
1212                 
1213         /* unlikely to happen, though we just cover it in case */
1214         return SCROLLHANDLE_BAR;
1215
1216
1217 /* initialise customdata for scroller manipulation operator */
1218 static void scroller_activate_init(bContext *C, wmOperator *op, wmEvent *event, short in_scroller)
1219 {
1220         v2dScrollerMove *vsm;
1221         View2DScrollers *scrollers;
1222         ARegion *ar= CTX_wm_region(C);
1223         View2D *v2d= &ar->v2d;
1224         float mask_size;
1225         int x, y;
1226         
1227         /* set custom-data for operator */
1228         vsm= MEM_callocN(sizeof(v2dScrollerMove), "v2dScrollerMove");
1229         op->customdata= vsm;
1230         
1231         /* set general data */
1232         vsm->v2d= v2d;
1233         vsm->scroller= in_scroller;
1234         
1235         /* store mouse-coordinates, and convert mouse/screen coordinates to region coordinates */
1236         vsm->lastx = event->x;
1237         vsm->lasty = event->y;
1238         x= event->x - ar->winrct.xmin;
1239         y= event->y - ar->winrct.ymin;
1240         
1241         /* 'zone' depends on where mouse is relative to bubble 
1242          *      - zooming must be allowed on this axis, otherwise, default to pan
1243          */
1244         scrollers= UI_view2d_scrollers_calc(C, v2d, V2D_ARG_DUMMY, V2D_ARG_DUMMY, V2D_ARG_DUMMY, V2D_ARG_DUMMY);
1245         if (in_scroller == 'h') {
1246                 /* horizontal scroller - calculate adjustment factor first */
1247                 mask_size= (float)(v2d->hor.xmax - v2d->hor.xmin);
1248                 vsm->fac= (v2d->tot.xmax - v2d->tot.xmin) / mask_size;
1249                 
1250                 /* get 'zone' (i.e. which part of scroller is activated) */
1251                 if (v2d->keepzoom & V2D_LOCKZOOM_X) {
1252                         /* default to scroll, as handles not usable */
1253                         vsm->zone= SCROLLHANDLE_BAR;
1254                 }
1255                 else {
1256                         /* check which handle we're in */
1257                         vsm->zone= mouse_in_scroller_handle(x, v2d->hor.xmin, v2d->hor.xmax, scrollers->hor_min, scrollers->hor_max); 
1258                 }
1259         }
1260         else {
1261                 /* vertical scroller - calculate adjustment factor first */
1262                 mask_size= (float)(v2d->vert.ymax - v2d->vert.ymin);
1263                 vsm->fac= (v2d->tot.ymax - v2d->tot.ymin) / mask_size;
1264                 
1265                 /* get 'zone' (i.e. which part of scroller is activated) */
1266                 if (v2d->keepzoom & V2D_LOCKZOOM_Y) {
1267                         /* default to scroll, as handles not usable */
1268                         vsm->zone= SCROLLHANDLE_BAR;
1269                 }
1270                 else {
1271                         /* check which handle we're in */
1272                         vsm->zone= mouse_in_scroller_handle(y, v2d->vert.ymin, v2d->vert.ymax, scrollers->vert_min, scrollers->vert_max); 
1273                 }
1274         }
1275         
1276         UI_view2d_scrollers_free(scrollers);
1277         ED_region_tag_redraw(ar);
1278 }
1279
1280 /* cleanup temp customdata  */
1281 static void scroller_activate_exit(bContext *C, wmOperator *op)
1282 {
1283         if (op->customdata) {
1284                 v2dScrollerMove *vsm= op->customdata;
1285
1286                 vsm->v2d->scroll_ui &= ~(V2D_SCROLL_H_ACTIVE|V2D_SCROLL_V_ACTIVE);
1287                 
1288                 MEM_freeN(op->customdata);
1289                 op->customdata= NULL;           
1290                 
1291                 ED_region_tag_redraw(CTX_wm_region(C));
1292         }
1293
1294
1295 /* apply transform to view (i.e. adjust 'cur' rect) */
1296 static void scroller_activate_apply(bContext *C, wmOperator *op)
1297 {
1298         v2dScrollerMove *vsm= op->customdata;
1299         View2D *v2d= vsm->v2d;
1300         float temp;
1301         
1302         /* calculate amount to move view by */
1303         temp= vsm->fac * vsm->delta;
1304         
1305         /* type of movement */
1306         switch (vsm->zone) {
1307                 case SCROLLHANDLE_MIN:
1308                         /* only expand view on axis if zoom is allowed */
1309                         if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X))
1310                                 v2d->cur.xmin -= temp;
1311                         if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y))
1312                                 v2d->cur.ymin -= temp;
1313                         break;
1314                         
1315                 case SCROLLHANDLE_MAX:
1316                         
1317                         /* only expand view on axis if zoom is allowed */
1318                         if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X))
1319                                 v2d->cur.xmax += temp;
1320                         if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y))
1321                                 v2d->cur.ymax += temp;
1322                         break;
1323                 
1324                 default: /* SCROLLHANDLE_BAR */
1325                         /* only move view on an axis if panning is allowed */
1326                         if ((vsm->scroller == 'h') && !(v2d->keepofs & V2D_LOCKOFS_X)) {
1327                                 v2d->cur.xmin += temp;
1328                                 v2d->cur.xmax += temp;
1329                         }
1330                         if ((vsm->scroller == 'v') && !(v2d->keepofs & V2D_LOCKOFS_Y)) {
1331                                 v2d->cur.ymin += temp;
1332                                 v2d->cur.ymax += temp;
1333                         }
1334                         break;
1335         }
1336         
1337         /* validate that view is in valid configuration after this operation */
1338         UI_view2d_curRect_validate(v2d);
1339         
1340         /* request updates to be done... */
1341         ED_area_tag_redraw(CTX_wm_area(C));
1342         UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
1343         WM_event_add_mousemove(C);
1344 }
1345
1346 /* handle user input for scrollers - calculations of mouse-movement need to be done here, not in the apply callback! */
1347 static int scroller_activate_modal(bContext *C, wmOperator *op, wmEvent *event)
1348 {
1349         v2dScrollerMove *vsm= op->customdata;
1350         
1351         /* execute the events */
1352         switch (event->type) {
1353                 case MOUSEMOVE:
1354                 {
1355                         /* calculate new delta transform, then store mouse-coordinates for next-time */
1356                         if (vsm->zone != SCROLLHANDLE_MIN) {
1357                                 /* if using bar (i.e. 'panning') or 'max' zoom widget */
1358                                 switch (vsm->scroller) {
1359                                         case 'h': /* horizontal scroller - so only horizontal movement ('cur' moves opposite to mouse) */
1360                                                 vsm->delta= (float)(event->x - vsm->lastx);
1361                                                 break;
1362                                         case 'v': /* vertical scroller - so only vertical movement ('cur' moves opposite to mouse) */
1363                                                 vsm->delta= (float)(event->y - vsm->lasty);
1364                                                 break;
1365                                 }
1366                         }
1367                         else {
1368                                 /* using 'min' zoom widget */
1369                                 switch (vsm->scroller) {
1370                                         case 'h': /* horizontal scroller - so only horizontal movement ('cur' moves with mouse) */
1371                                                 vsm->delta= (float)(vsm->lastx - event->x);
1372                                                 break;
1373                                         case 'v': /* vertical scroller - so only vertical movement ('cur' moves with to mouse) */
1374                                                 vsm->delta= (float)(vsm->lasty - event->y);
1375                                                 break;
1376                                 }
1377                         }
1378                         
1379                         /* store previous coordinates */
1380                         vsm->lastx= event->x;
1381                         vsm->lasty= event->y;
1382                         
1383                         scroller_activate_apply(C, op);
1384                 }
1385                         break;
1386                         
1387                 case LEFTMOUSE:
1388                         if (event->val==KM_RELEASE) {
1389                                 scroller_activate_exit(C, op);
1390                                 return OPERATOR_FINISHED;
1391                         }
1392                         break;
1393         }
1394
1395         return OPERATOR_RUNNING_MODAL;
1396 }
1397
1398
1399 /* a click (or click drag in progress) should have occurred, so check if it happened in scrollbar */
1400 static int scroller_activate_invoke(bContext *C, wmOperator *op, wmEvent *event)
1401 {
1402         ARegion *ar= CTX_wm_region(C);
1403         View2D *v2d= &ar->v2d;
1404         short in_scroller= 0;
1405                 
1406         /* check if mouse in scrollbars, if they're enabled */
1407         in_scroller= UI_view2d_mouse_in_scrollers(C, v2d, event->x, event->y);
1408         
1409         /* if in a scroller, init customdata then set modal handler which will catch mousedown to start doing useful stuff */
1410         if (in_scroller) {
1411                 v2dScrollerMove *vsm;
1412                 
1413                 /* initialise customdata */
1414                 scroller_activate_init(C, op, event, in_scroller);
1415                 vsm= (v2dScrollerMove *)op->customdata;
1416                 
1417                 /* check if zone is inappropriate (i.e. 'bar' but panning is banned), so cannot continue */
1418                 if (vsm->zone == SCROLLHANDLE_BAR) {
1419                         if ( ((vsm->scroller=='h') && (v2d->keepofs & V2D_LOCKOFS_X)) ||
1420                                  ((vsm->scroller=='v') && (v2d->keepofs & V2D_LOCKOFS_Y)) )
1421                         {
1422                                 /* free customdata initialised */
1423                                 scroller_activate_exit(C, op);
1424                                 
1425                                 /* can't catch this event for ourselves, so let it go to someone else? */
1426                                 return OPERATOR_PASS_THROUGH;
1427                         }                       
1428                 }
1429                 /* zone is also inappropriate if scroller is not visible... */
1430                 if ( ((vsm->scroller=='h') && (v2d->scroll & V2D_SCROLL_HORIZONTAL_HIDE)) ||
1431                          ((vsm->scroller=='v') && (v2d->scroll & V2D_SCROLL_VERTICAL_HIDE)) )
1432                 {
1433                         /* can't catch this event for ourselves, so let it go to someone else? */
1434                         return OPERATOR_PASS_THROUGH;
1435                 }
1436                 
1437                 if(vsm->scroller=='h')
1438                         v2d->scroll_ui |= V2D_SCROLL_H_ACTIVE;
1439                 else
1440                         v2d->scroll_ui |= V2D_SCROLL_V_ACTIVE;
1441                 
1442                 /* still ok, so can add */
1443                 WM_event_add_modal_handler(C, op);
1444                 return OPERATOR_RUNNING_MODAL;
1445         }
1446         else {
1447                 /* not in scroller, so nothing happened... (pass through let's something else catch event) */
1448                 return OPERATOR_PASS_THROUGH;
1449         }
1450 }
1451
1452 /* LMB-Drag in Scrollers - not repeatable operator! */
1453 void VIEW2D_OT_scroller_activate(wmOperatorType *ot)
1454 {
1455         /* identifiers */
1456         ot->name= "Scroller Activate";
1457         ot->description= "Scroll view by mouse click and drag.";
1458         ot->idname= "VIEW2D_OT_scroller_activate";
1459
1460         /* flags */
1461         ot->flag= OPTYPE_BLOCKING;
1462         
1463         /* api callbacks */
1464         ot->invoke= scroller_activate_invoke;
1465         ot->modal= scroller_activate_modal;
1466         ot->poll= view2d_poll;
1467 }
1468
1469 /* ********************************************************* */
1470 /* RESET */
1471
1472 static int reset_exec(bContext *C, wmOperator *op)
1473 {
1474         uiStyle *style= U.uistyles.first;
1475         ARegion *ar= CTX_wm_region(C);
1476         View2D *v2d= &ar->v2d;
1477         int winx, winy;
1478
1479         /* zoom 1.0 */
1480         winx= (float)(v2d->mask.xmax - v2d->mask.xmin + 1);
1481         winy= (float)(v2d->mask.ymax - v2d->mask.ymin + 1);
1482
1483         v2d->cur.xmax= v2d->cur.xmin + winx;
1484         v2d->cur.ymax= v2d->cur.ymin + winy;
1485         
1486         /* align */
1487         if(v2d->align) {
1488                 /* posx and negx flags are mutually exclusive, so watch out */
1489                 if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) {
1490                         v2d->cur.xmax= 0.0f;
1491                         v2d->cur.xmin= -winx*style->panelzoom;
1492                 }
1493                 else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) {
1494                         v2d->cur.xmax= winx*style->panelzoom;
1495                         v2d->cur.xmin= 0.0f;
1496                 }
1497
1498                 /* - posx and negx flags are mutually exclusive, so watch out */
1499                 if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) {
1500                         v2d->cur.ymax= 0.0f;
1501                         v2d->cur.ymin= -winy*style->panelzoom;
1502                 }
1503                 else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) {
1504                         v2d->cur.ymax= winy*style->panelzoom;
1505                         v2d->cur.ymin= 0.0f;
1506                 }
1507         }
1508
1509         /* validate that view is in valid configuration after this operation */
1510         UI_view2d_curRect_validate(v2d);
1511         
1512         /* request updates to be done... */
1513         ED_area_tag_redraw(CTX_wm_area(C));
1514         UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
1515         WM_event_add_mousemove(C);
1516         
1517         return OPERATOR_FINISHED;
1518 }
1519  
1520 void VIEW2D_OT_reset(wmOperatorType *ot)
1521 {
1522         /* identifiers */
1523         ot->name= "Reset View";
1524         ot->description= "Reset the view.";
1525         ot->idname= "VIEW2D_OT_reset";
1526         
1527         /* api callbacks */
1528         ot->exec= reset_exec;
1529         ot->poll= view2d_poll;
1530         
1531         /* flags */
1532         // ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
1533 }
1534  
1535 /* ********************************************************* */
1536 /* Registration */
1537
1538 void UI_view2d_operatortypes(void)
1539 {
1540         WM_operatortype_append(VIEW2D_OT_pan);
1541         
1542         WM_operatortype_append(VIEW2D_OT_scroll_left);
1543         WM_operatortype_append(VIEW2D_OT_scroll_right);
1544         WM_operatortype_append(VIEW2D_OT_scroll_up);
1545         WM_operatortype_append(VIEW2D_OT_scroll_down);
1546         
1547         WM_operatortype_append(VIEW2D_OT_zoom_in);
1548         WM_operatortype_append(VIEW2D_OT_zoom_out);
1549         
1550         WM_operatortype_append(VIEW2D_OT_zoom);
1551         WM_operatortype_append(VIEW2D_OT_zoom_border);
1552         
1553         WM_operatortype_append(VIEW2D_OT_scroller_activate);
1554
1555         WM_operatortype_append(VIEW2D_OT_reset);
1556 }
1557
1558 void UI_view2d_keymap(wmKeyConfig *keyconf)
1559 {
1560         wmKeyMap *keymap= WM_keymap_find(keyconf, "View2D", 0, 0);
1561         
1562         /* pan/scroll */
1563         WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MIDDLEMOUSE, KM_PRESS, 0, 0);
1564         WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MIDDLEMOUSE, KM_PRESS, KM_SHIFT, 0);
1565         
1566         WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MOUSEPAN, 0, 0, 0);
1567         
1568         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_right", WHEELDOWNMOUSE, KM_PRESS, KM_CTRL, 0);
1569         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_left", WHEELUPMOUSE, KM_PRESS, KM_CTRL, 0);
1570         
1571         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_down", WHEELDOWNMOUSE, KM_PRESS, KM_SHIFT, 0);
1572         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_up", WHEELUPMOUSE, KM_PRESS, KM_SHIFT, 0);
1573         
1574         /* zoom - single step */
1575         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_out", WHEELOUTMOUSE, KM_PRESS, 0, 0);
1576         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_in", WHEELINMOUSE, KM_PRESS, 0, 0);
1577         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_out", PADMINUS, KM_PRESS, 0, 0);
1578         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_in", PADPLUSKEY, KM_PRESS, 0, 0);
1579         
1580         /* scroll up/down - no modifiers, only when zoom fails */
1581                 /* these may fail if zoom is disallowed, in which case they should pass on event */
1582         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_down", WHEELDOWNMOUSE, KM_PRESS, 0, 0);
1583         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_up", WHEELUPMOUSE, KM_PRESS, 0, 0);
1584                 /* these may be necessary if vertical scroll is disallowed */
1585         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_right", WHEELDOWNMOUSE, KM_PRESS, 0, 0);
1586         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_left", WHEELUPMOUSE, KM_PRESS, 0, 0);
1587         
1588         /* zoom - drag */
1589         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MIDDLEMOUSE, KM_PRESS, KM_CTRL, 0);
1590         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MOUSEZOOM, 0, 0, 0);
1591         
1592         /* borderzoom - drag */
1593         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_border", BKEY, KM_PRESS, KM_SHIFT, 0);
1594         
1595         /* scrollers */
1596         WM_keymap_add_item(keymap, "VIEW2D_OT_scroller_activate", LEFTMOUSE, KM_PRESS, 0, 0);
1597
1598         /* Alternative keymap for buttons listview */
1599         keymap= WM_keymap_find(keyconf, "View2D Buttons List", 0, 0);
1600         WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MIDDLEMOUSE, KM_PRESS, 0, 0);
1601         WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MOUSEPAN, 0, 0, 0);
1602         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_down", WHEELDOWNMOUSE, KM_PRESS, 0, 0);
1603         WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_up", WHEELUPMOUSE, KM_PRESS, 0, 0);
1604         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MIDDLEMOUSE, KM_PRESS, KM_CTRL, 0);
1605         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MOUSEZOOM, 0, 0, 0);
1606         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_out", PADMINUS, KM_PRESS, 0, 0);
1607         WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_in", PADPLUSKEY, KM_PRESS, 0, 0);
1608         WM_keymap_add_item(keymap, "VIEW2D_OT_reset", HOMEKEY, KM_PRESS, 0, 0);
1609         WM_keymap_add_item(keymap, "VIEW2D_OT_scroller_activate", LEFTMOUSE, KM_PRESS, 0, 0);
1610 }
1611