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