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