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