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