2.5
[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_view2d_types.h"
37
38 #include "BLI_blenlib.h"
39
40 #include "BKE_global.h"
41 #include "BKE_utildefines.h"
42
43 #include "RNA_access.h"
44 #include "RNA_define.h"
45
46 #include "WM_api.h"
47 #include "WM_types.h"
48
49 #include "BIF_gl.h"
50
51 #include "UI_resources.h"
52 #include "UI_view2d.h"
53
54 /* ********************************************************* */
55 /* General Polling Funcs */
56
57 /* Check if mouse is within scrollbars 
58  *      - Returns appropriate code for match
59  *              'h' = in horizontal scrollbar
60  *              'v' = in vertical scrollbar
61  *              0 = not in scrollbar
62  *      
63  *      - x,y   = mouse coordinates in screen (not region) space
64  */
65 static short mouse_in_v2d_scrollers (const bContext *C, View2D *v2d, int x, int y)
66 {
67         ARegion *ar= C->region;
68         int co[2];
69         
70         /* clamp x,y to region-coordinates first */
71         co[0]= x - ar->winrct.xmin;
72         co[1]= y - ar->winrct.ymin;
73         
74         /* check if within scrollbars */
75         if (v2d->scroll & V2D_SCROLL_HORIZONTAL) {
76                 if (IN_2D_HORIZ_SCROLL(v2d, co)) return 'h';
77         }
78         if (v2d->scroll & V2D_SCROLL_VERTICAL) {
79                 if (IN_2D_VERT_SCROLL(v2d, co)) return 'v';
80         }       
81         
82         /* not found */
83         return 0;
84
85
86
87 /* ********************************************************* */
88 /* VIEW PANNING OPERATOR                                                                 */
89
90 /*      This group of operators come in several forms:
91  *              1) Modal 'dragging' with MMB - where movement of mouse dictates amount to pan view by
92  *              2) Scrollwheel 'steps' - rolling mousewheel by one step moves view by predefined amount
93  *
94  *      In order to make sure this works, each operator must define the following RNA-Operator Props:
95  *              deltax, deltay  - define how much to move view by (relative to zoom-correction factor)
96  */
97
98 /* ------------------ Shared 'core' stuff ---------------------- */
99  
100 /* temp customdata for operator */
101 typedef struct v2dViewPanData {
102         View2D *v2d;                    /* view2d we're operating in */
103         
104         float facx, facy;               /* amount to move view relative to zoom */
105         
106                 /* options for version 1 */
107         int startx, starty;             /* mouse x/y values in window when operator was initiated */
108         int lastx, lasty;               /* previous x/y values of mouse in window */
109         
110         short in_scroller;              /* for MMB in scrollers (old feature in past, but now not that useful) */
111 } v2dViewPanData;
112  
113 /* initialise panning customdata */
114 static int view_pan_init(bContext *C, wmOperator *op)
115 {
116         v2dViewPanData *vpd;
117         ARegion *ar;
118         View2D *v2d;
119         float winx, winy;
120         
121         /* regions now have v2d-data by default, so check for region */
122         if (C->region == NULL)
123                 return 0;
124         ar= C->region;
125         
126         /* set custom-data for operator */
127         vpd= MEM_callocN(sizeof(v2dViewPanData), "v2dViewPanData");
128         op->customdata= vpd;
129         
130         /* set pointers to owners */
131         vpd->v2d= v2d= &ar->v2d;
132         
133         /* calculate translation factor - based on size of view */
134         winx= (float)(ar->winrct.xmax - ar->winrct.xmin);
135         winy= (float)(ar->winrct.ymax - ar->winrct.ymin);
136         vpd->facx= (v2d->cur.xmax - v2d->cur.xmin) / winx;
137         vpd->facy= (v2d->cur.ymax - v2d->cur.ymin) / winy;
138         
139         return 1;
140 }
141
142 /* apply transform to view (i.e. adjust 'cur' rect) */
143 static void view_pan_apply(bContext *C, wmOperator *op)
144 {
145         v2dViewPanData *vpd= op->customdata;
146         View2D *v2d= vpd->v2d;
147         float dx, dy;
148         
149         /* calculate amount to move view by */
150         dx= vpd->facx * (float)RNA_int_get(op->ptr, "deltax");
151         dy= vpd->facy * (float)RNA_int_get(op->ptr, "deltay");
152         
153         /* only move view on an axis if change is allowed */
154         if ((v2d->keepofs & V2D_LOCKOFS_X)==0) {
155                 v2d->cur.xmin += dx;
156                 v2d->cur.xmax += dx;
157         }
158         if ((v2d->keepofs & V2D_LOCKOFS_Y)==0) {
159                 v2d->cur.ymin += dy;
160                 v2d->cur.ymax += dy;
161         }
162         
163         /* validate that view is in valid configuration after this operation */
164         UI_view2d_curRect_validate(v2d);
165         
166         /* request updates to be done... */
167         WM_event_add_notifier(C, WM_NOTE_AREA_REDRAW, 0, NULL);
168         /* XXX: add WM_NOTE_TIME_CHANGED? */
169 }
170
171 /* cleanup temp customdata  */
172 static void view_pan_exit(bContext *C, wmOperator *op)
173 {
174         if (op->customdata) {
175                 MEM_freeN(op->customdata);
176                 op->customdata= NULL;                           
177         }
178
179  
180 /* ------------------ Modal Drag Version (1) ---------------------- */
181
182 /* for 'redo' only, with no user input */
183 static int view_pan_exec(bContext *C, wmOperator *op)
184 {
185         if (!view_pan_init(C, op))
186                 return OPERATOR_CANCELLED;
187         
188         view_pan_apply(C, op);
189         view_pan_exit(C, op);
190         return OPERATOR_FINISHED;
191 }
192
193 /* set up modal operator and relevant settings */
194 static int view_pan_invoke(bContext *C, wmOperator *op, wmEvent *event)
195 {
196         v2dViewPanData *vpd;
197         View2D *v2d;
198         
199         /* set up customdata */
200         if (!view_pan_init(C, op))
201                 return OPERATOR_CANCELLED;
202         
203         vpd= op->customdata;
204         v2d= vpd->v2d;
205         
206         /* set initial settings */
207         vpd->startx= vpd->lastx= event->x;
208         vpd->starty= vpd->lasty= event->y;
209         RNA_int_set(op->ptr, "deltax", 0);
210         RNA_int_set(op->ptr, "deltay", 0);
211         
212         if (v2d->keepofs & V2D_LOCKOFS_X)
213                 WM_cursor_modal(C->window, BC_NS_SCROLLCURSOR);
214         else if (v2d->keepofs & V2D_LOCKOFS_Y)
215                 WM_cursor_modal(C->window, BC_EW_SCROLLCURSOR);
216         else
217                 WM_cursor_modal(C->window, BC_NSEW_SCROLLCURSOR);
218         
219         /* add temp handler */
220         WM_event_add_modal_handler(C, &C->window->handlers, op);
221
222         return OPERATOR_RUNNING_MODAL;
223 }
224
225 /* handle user input - calculations of mouse-movement need to be done here, not in the apply callback! */
226 static int view_pan_modal(bContext *C, wmOperator *op, wmEvent *event)
227 {
228         v2dViewPanData *vpd= op->customdata;
229         
230         /* execute the events */
231         switch (event->type) {
232                 case MOUSEMOVE:
233                 {
234                         /* calculate new delta transform, then store mouse-coordinates for next-time */
235                         RNA_int_set(op->ptr, "deltax", (vpd->lastx - event->x));
236                         RNA_int_set(op->ptr, "deltay", (vpd->lasty - event->y));
237                         
238                         vpd->lastx= event->x;
239                         vpd->lasty= event->y;
240                         
241                         view_pan_apply(C, op);
242                 }
243                         break;
244                         
245                 case MIDDLEMOUSE:
246                         if (event->val==0) {
247                                 /* calculate overall delta mouse-movement for redo */
248                                 RNA_int_set(op->ptr, "deltax", (vpd->startx - vpd->lastx));
249                                 RNA_int_set(op->ptr, "deltay", (vpd->starty - vpd->lasty));
250                                 
251                                 view_pan_exit(C, op);
252                                 WM_cursor_restore(C->window);
253                                 
254                                 return OPERATOR_FINISHED;
255                         }
256                         break;
257         }
258
259         return OPERATOR_RUNNING_MODAL;
260 }
261
262 void ED_View2D_OT_view_pan(wmOperatorType *ot)
263 {
264         PropertyRNA *prop;
265         
266         /* identifiers */
267         ot->name= "Pan View";
268         ot->idname= "ED_View2D_OT_view_pan";
269         
270         /* api callbacks */
271         ot->exec= view_pan_exec;
272         ot->invoke= view_pan_invoke;
273         ot->modal= view_pan_modal;
274         
275         /* operator is repeatable */
276         ot->flag= OPTYPE_REGISTER;
277         
278         /* rna - must keep these in sync with the other operators */
279         prop= RNA_def_property(ot->srna, "deltax", PROP_INT, PROP_NONE);
280         prop= RNA_def_property(ot->srna, "deltay", PROP_INT, PROP_NONE);
281 }
282
283 /* ------------------ Scrollwheel Versions (2) ---------------------- */
284
285 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
286 static int view_scrollright_exec(bContext *C, wmOperator *op)
287 {
288         /* initialise default settings (and validate if ok to run) */
289         if (!view_pan_init(C, op))
290                 return OPERATOR_CANCELLED;
291         
292         /* set RNA-Props - only movement in positive x-direction */
293         RNA_int_set(op->ptr, "deltax", 20);
294         RNA_int_set(op->ptr, "deltay", 0);
295         
296         /* apply movement, then we're done */
297         view_pan_apply(C, op);
298         view_pan_exit(C, op);
299         
300         return OPERATOR_FINISHED;
301 }
302
303 void ED_View2D_OT_view_scrollright(wmOperatorType *ot)
304 {
305         PropertyRNA *prop;
306         
307         /* identifiers */
308         ot->name= "Scroll Right";
309         ot->idname= "ED_View2D_OT_view_rightscroll";
310         
311         /* api callbacks */
312         ot->exec= view_scrollright_exec;
313         
314         /* operator is repeatable */
315         ot->flag= OPTYPE_REGISTER;
316         
317         /* rna - must keep these in sync with the other operators */
318         prop= RNA_def_property(ot->srna, "deltax", PROP_INT, PROP_NONE);
319         prop= RNA_def_property(ot->srna, "deltay", PROP_INT, PROP_NONE);
320 }
321
322
323
324 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
325 static int view_scrollleft_exec(bContext *C, wmOperator *op)
326 {
327         /* initialise default settings (and validate if ok to run) */
328         if (!view_pan_init(C, op))
329                 return OPERATOR_CANCELLED;
330         
331         /* set RNA-Props - only movement in negative x-direction */
332         RNA_int_set(op->ptr, "deltax", -20);
333         RNA_int_set(op->ptr, "deltay", 0);
334         
335         /* apply movement, then we're done */
336         view_pan_apply(C, op);
337         view_pan_exit(C, op);
338         
339         return OPERATOR_FINISHED;
340 }
341
342 void ED_View2D_OT_view_scrollleft(wmOperatorType *ot)
343 {
344         PropertyRNA *prop;
345         
346         /* identifiers */
347         ot->name= "Scroll Left";
348         ot->idname= "ED_View2D_OT_view_leftscroll";
349         
350         /* api callbacks */
351         ot->exec= view_scrollleft_exec;
352         
353         /* operator is repeatable */
354         ot->flag= OPTYPE_REGISTER;
355         
356         /* rna - must keep these in sync with the other operators */
357         prop= RNA_def_property(ot->srna, "deltax", PROP_INT, PROP_NONE);
358         prop= RNA_def_property(ot->srna, "deltay", PROP_INT, PROP_NONE);
359 }
360
361 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
362 static int view_scrolldown_exec(bContext *C, wmOperator *op)
363 {
364         /* initialise default settings (and validate if ok to run) */
365         if (!view_pan_init(C, op))
366                 return OPERATOR_CANCELLED;
367         
368         /* set RNA-Props - only movement in positive x-direction */
369         RNA_int_set(op->ptr, "deltax", 0);
370         RNA_int_set(op->ptr, "deltay", -20);
371         
372         /* apply movement, then we're done */
373         view_pan_apply(C, op);
374         view_pan_exit(C, op);
375         
376         return OPERATOR_FINISHED;
377 }
378
379 void ED_View2D_OT_view_scrolldown(wmOperatorType *ot)
380 {
381         PropertyRNA *prop;
382         
383         /* identifiers */
384         ot->name= "Scroll Down";
385         ot->idname= "ED_View2D_OT_view_downscroll";
386         
387         /* api callbacks */
388         ot->exec= view_scrolldown_exec;
389         
390         /* operator is repeatable */
391         ot->flag= OPTYPE_REGISTER;
392         
393         /* rna - must keep these in sync with the other operators */
394         prop= RNA_def_property(ot->srna, "deltax", PROP_INT, PROP_NONE);
395         prop= RNA_def_property(ot->srna, "deltay", PROP_INT, PROP_NONE);
396 }
397
398
399
400 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
401 static int view_scrollup_exec(bContext *C, wmOperator *op)
402 {
403         /* initialise default settings (and validate if ok to run) */
404         if (!view_pan_init(C, op))
405                 return OPERATOR_CANCELLED;
406         
407         /* set RNA-Props - only movement in negative x-direction */
408         RNA_int_set(op->ptr, "deltax", 0);
409         RNA_int_set(op->ptr, "deltay", 20);
410         
411         /* apply movement, then we're done */
412         view_pan_apply(C, op);
413         view_pan_exit(C, op);
414         
415         return OPERATOR_FINISHED;
416 }
417
418 void ED_View2D_OT_view_scrollup(wmOperatorType *ot)
419 {
420         PropertyRNA *prop;
421         
422         /* identifiers */
423         ot->name= "Scroll Up";
424         ot->idname= "ED_View2D_OT_view_upscroll";
425         
426         /* api callbacks */
427         ot->exec= view_scrollup_exec;
428         
429         /* operator is repeatable */
430         ot->flag= OPTYPE_REGISTER;
431         
432         /* rna - must keep these in sync with the other operators */
433         prop= RNA_def_property(ot->srna, "deltax", PROP_INT, PROP_NONE);
434         prop= RNA_def_property(ot->srna, "deltay", PROP_INT, PROP_NONE);
435 }
436
437 /* ********************************************************* */
438 /* SINGLE-STEP VIEW ZOOMING OPERATOR                                             */
439
440 /*      This group of operators come in several forms:
441  *              1) Scrollwheel 'steps' - rolling mousewheel by one step zooms view by predefined amount
442  *              2) Scrollwheel 'steps' + alt + ctrl/shift - zooms view on one axis only (ctrl=x, shift=y)  // XXX this could be implemented...
443  *              3) Pad +/- Keys - pressing each key moves the zooms the view by a predefined amount
444  *
445  *      In order to make sure this works, each operator must define the following RNA-Operator Props:
446  *              zoomfacx, zoomfacy      - These two zoom factors allow for non-uniform scaling.
447  *                                                        It is safe to scale by 0, as these factors are used to determine
448  *                                                        amount to enlarge 'cur' by
449  */
450
451 /* ------------------ 'Shared' stuff ------------------------ */
452  
453 /* check if step-zoom can be applied */
454 static short view_zoomstep_ok(bContext *C)
455 {
456         View2D *v2d;
457         
458         /* check if there's a region in context to work with */
459         if (C->region == NULL)
460                 return 0;
461         v2d= &C->region->v2d;
462         
463         /* check that 2d-view is zoomable */
464         if ((v2d->keepzoom & V2D_LOCKZOOM_X) && (v2d->keepzoom & V2D_LOCKZOOM_Y))
465                 return 0;
466                 
467         /* view is zoomable */
468         return 1;
469 }
470  
471 /* apply transform to view (i.e. adjust 'cur' rect) */
472 static void view_zoomstep_apply(bContext *C, wmOperator *op)
473 {
474         View2D *v2d= &C->region->v2d;
475         float dx, dy;
476         
477         /* calculate amount to move view by */
478         dx= (v2d->cur.xmax - v2d->cur.xmin) * (float)RNA_float_get(op->ptr, "zoomfacx");
479         dy= (v2d->cur.ymax - v2d->cur.ymin) * (float)RNA_float_get(op->ptr, "zoomfacy");
480         
481         /* only move view on an axis if change is allowed */
482         if ((v2d->keepzoom & V2D_LOCKOFS_X)==0) {
483                 v2d->cur.xmin += dx;
484                 v2d->cur.xmax -= dx;
485         }
486         if ((v2d->keepzoom & V2D_LOCKOFS_Y)==0) {
487                 v2d->cur.ymin += dy;
488                 v2d->cur.ymax -= dy;
489         }
490         
491         /* validate that view is in valid configuration after this operation */
492         UI_view2d_curRect_validate(v2d);
493         
494         /* request updates to be done... */
495         WM_event_add_notifier(C, WM_NOTE_AREA_REDRAW, 0, NULL);
496         /* XXX: add WM_NOTE_TIME_CHANGED? */
497 }
498
499 /* --------------- Individual Operators ------------------- */
500
501 /* this operator only needs this single callback, where it calls the view_zoom_*() methods */
502 static int view_zoomin_exec(bContext *C, wmOperator *op)
503 {
504         /* check that there's an active region, as View2D data resides there */
505         if (!view_zoomstep_ok(C))
506                 return OPERATOR_CANCELLED;
507         
508         /* set RNA-Props - zooming in by uniform factor */
509         RNA_float_set(op->ptr, "zoomfacx", 0.0375f);
510         RNA_float_set(op->ptr, "zoomfacy", 0.0375f);
511         
512         /* apply movement, then we're done */
513         view_zoomstep_apply(C, op);
514         
515         return OPERATOR_FINISHED;
516 }
517
518 void ED_View2D_OT_view_zoomin(wmOperatorType *ot)
519 {
520         PropertyRNA *prop;
521         
522         /* identifiers */
523         ot->name= "Zoom In";
524         ot->idname= "ED_View2D_OT_view_zoomin";
525         
526         /* api callbacks */
527         ot->exec= view_zoomin_exec;
528         
529         /* operator is repeatable */
530         ot->flag= OPTYPE_REGISTER;
531         
532         /* rna - must keep these in sync with the other operators */
533         prop= RNA_def_property(ot->srna, "zoomfacx", PROP_FLOAT, PROP_NONE);
534         prop= RNA_def_property(ot->srna, "zoomfacy", PROP_FLOAT, PROP_NONE);
535 }
536
537
538
539 /* this operator only needs this single callback, where it callsthe view_zoom_*() methods */
540 static int view_zoomout_exec(bContext *C, wmOperator *op)
541 {
542         /* check that there's an active region, as View2D data resides there */
543         if (!view_zoomstep_ok(C))
544                 return OPERATOR_CANCELLED;
545         
546         /* set RNA-Props - zooming in by uniform factor */
547         RNA_float_set(op->ptr, "zoomfacx", -0.0375f);
548         RNA_float_set(op->ptr, "zoomfacy", -0.0375f);
549         
550         /* apply movement, then we're done */
551         view_zoomstep_apply(C, op);
552         
553         return OPERATOR_FINISHED;
554 }
555
556 void ED_View2D_OT_view_zoomout(wmOperatorType *ot)
557 {
558         PropertyRNA *prop;
559         
560         /* identifiers */
561         ot->name= "Zoom Out";
562         ot->idname= "ED_View2D_OT_view_zoomout";
563         
564         /* api callbacks */
565         ot->exec= view_zoomout_exec;
566         
567         /* operator is repeatable */
568         ot->flag= OPTYPE_REGISTER;
569         
570         /* rna - must keep these in sync with the other operators */
571         prop= RNA_def_property(ot->srna, "zoomfacx", PROP_FLOAT, PROP_NONE);
572         prop= RNA_def_property(ot->srna, "zoomfacy", PROP_FLOAT, PROP_NONE);
573 }
574
575 /* ********************************************************* */
576 /* DRAG-ZOOM OPERATOR                                                                    */
577
578 /*      This group of operators come in several forms:
579  *              1) MMB Drag - allows non-uniform scaling by dragging mouse
580  *                                 - method of scaling depends upon U.viewzoom setting (Continue, Dolly, Scale)
581  *                                      XXX should we store this info as RNA prop?
582  *
583  *      In order to make sure this works, each operator must define the following RNA-Operator Props:
584  *              deltax, deltay  - amounts to add to each side of the 'cur' rect
585  */
586  
587 /* ------------------ Shared 'core' stuff ---------------------- */
588  
589 /* temp customdata for operator */
590 typedef struct v2dViewZoomData {
591         View2D *v2d;                    /* view2d we're operating in */
592         
593         int lastx, lasty;               /* previous x/y values of mouse in window */
594         float dx, dy;                   /* running tally of previous delta values (for obtaining final zoom) */
595 } v2dViewZoomData;
596  
597 /* initialise panning customdata */
598 static int view_zoomdrag_init(bContext *C, wmOperator *op)
599 {
600         v2dViewZoomData *vzd;
601         View2D *v2d;
602         
603         /* regions now have v2d-data by default, so check for region */
604         if (C->region == NULL)
605                 return 0;
606         v2d= &C->region->v2d;
607         
608         /* check that 2d-view is zoomable */
609         if ((v2d->keepzoom & V2D_LOCKZOOM_X) && (v2d->keepzoom & V2D_LOCKZOOM_Y))
610                 return 0;
611         
612         /* set custom-data for operator */
613         vzd= MEM_callocN(sizeof(v2dViewZoomData), "v2dViewZoomData");
614         op->customdata= vzd;
615         
616         /* set pointers to owners */
617         vzd->v2d= v2d;
618         
619         return 1;
620 }
621
622 /* apply transform to view (i.e. adjust 'cur' rect) */
623 static void view_zoomdrag_apply(bContext *C, wmOperator *op)
624 {
625         v2dViewZoomData *vzd= op->customdata;
626         View2D *v2d= vzd->v2d;
627         float dx, dy;
628         
629         /* get amount to move view by */
630         dx= RNA_float_get(op->ptr, "deltax");
631         dy= RNA_float_get(op->ptr, "deltay");
632         
633         /* only move view on an axis if change is allowed */
634         if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0) {
635                 v2d->cur.xmin += dx;
636                 v2d->cur.xmax -= dx;
637         }
638         if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0) {
639                 v2d->cur.ymin += dy;
640                 v2d->cur.ymax -= dy;
641         }
642         
643         /* validate that view is in valid configuration after this operation */
644         UI_view2d_curRect_validate(v2d);
645         
646         /* request updates to be done... */
647         WM_event_add_notifier(C, WM_NOTE_AREA_REDRAW, 0, NULL);
648         /* XXX: add WM_NOTE_TIME_CHANGED? */
649 }
650
651 /* cleanup temp customdata  */
652 static void view_zoomdrag_exit(bContext *C, wmOperator *op)
653 {
654         if (op->customdata) {
655                 MEM_freeN(op->customdata);
656                 op->customdata= NULL;                           
657         }
658
659
660 /* for 'redo' only, with no user input */
661 static int view_zoomdrag_exec(bContext *C, wmOperator *op)
662 {
663         if (!view_zoomdrag_init(C, op))
664                 return OPERATOR_CANCELLED;
665         
666         view_zoomdrag_apply(C, op);
667         view_zoomdrag_exit(C, op);
668         return OPERATOR_FINISHED;
669 }
670
671 /* set up modal operator and relevant settings */
672 static int view_zoomdrag_invoke(bContext *C, wmOperator *op, wmEvent *event)
673 {
674         v2dViewZoomData *vzd;
675         View2D *v2d;
676         
677         /* set up customdata */
678         if (!view_zoomdrag_init(C, op))
679                 return OPERATOR_CANCELLED;
680         
681         vzd= op->customdata;
682         v2d= vzd->v2d;
683         
684         /* set initial settings */
685         vzd->lastx= event->x;
686         vzd->lasty= event->y;
687         RNA_float_set(op->ptr, "deltax", 0);
688         RNA_float_set(op->ptr, "deltay", 0);
689         
690         if (v2d->keepofs & V2D_LOCKOFS_X)
691                 WM_cursor_modal(C->window, BC_NS_SCROLLCURSOR);
692         else if (v2d->keepofs & V2D_LOCKOFS_Y)
693                 WM_cursor_modal(C->window, BC_EW_SCROLLCURSOR);
694         else
695                 WM_cursor_modal(C->window, BC_NSEW_SCROLLCURSOR);
696         
697         /* add temp handler */
698         WM_event_add_modal_handler(C, &C->window->handlers, op);
699
700         return OPERATOR_RUNNING_MODAL;
701 }
702
703 /* handle user input - calculations of mouse-movement need to be done here, not in the apply callback! */
704 static int view_zoomdrag_modal(bContext *C, wmOperator *op, wmEvent *event)
705 {
706         v2dViewZoomData *vzd= op->customdata;
707         View2D *v2d= vzd->v2d;
708         
709         /* execute the events */
710         switch (event->type) {
711                 case MOUSEMOVE:
712                 {
713                         float dx, dy;
714                         
715                         /* calculate new delta transform, based on zooming mode */
716                         if (U.viewzoom == USER_ZOOM_SCALE) { // XXX store this setting as RNA prop?
717                                 /* 'scale' zooming */
718                                 float dist;
719                                 
720                                 /* x-axis transform */
721                                 dist = (v2d->mask.xmax - v2d->mask.xmin) / 2.0f;
722                                 dx= 1.0f - (fabs(vzd->lastx - dist) + 2.0f) / (fabs(event->x - dist) + 2.0f);
723                                 dx*= 0.5f * (v2d->cur.xmax - v2d->cur.xmin);
724                                 
725                                 /* y-axis transform */
726                                 dist = (v2d->mask.ymax - v2d->mask.ymin) / 2.0f;
727                                 dy= 1.0f - (fabs(vzd->lasty - dist) + 2.0) / (fabs(event->y - dist) + 2.0f);
728                                 dy*= 0.5f * (v2d->cur.ymax - v2d->cur.ymin);
729                         }
730                         else {
731                                 /* 'continuous' or 'dolly' */
732                                 float fac;
733                                 
734                                 /* x-axis transform */
735                                 fac= 0.01f * (event->x - vzd->lastx);
736                                 dx= fac * (v2d->cur.xmax - v2d->cur.xmin);
737                                 
738                                 /* y-axis transform */
739                                 fac= 0.01f * (event->y - vzd->lasty);
740                                 dy= fac * (v2d->cur.ymax - v2d->cur.ymin);
741                                 
742                                 /* continous zoom shouldn't move that fast... */
743                                 if (U.viewzoom == USER_ZOOM_CONT) { // XXX store this setting as RNA prop?
744                                         dx /= 20.0f;
745                                         dy /= 20.0f;
746                                 }
747                         }
748                         
749                         /* set transform amount, and add current deltas to stored total delta (for redo) */
750                         RNA_float_set(op->ptr, "deltax", dx);
751                         RNA_float_set(op->ptr, "deltay", dy);
752                         vzd->dx += dx;
753                         vzd->dy += dy;
754                         
755                         /* store mouse coordinates for next time, if not doing continuous zoom
756                          *      - continuous zoom only depends on distance of mouse to starting point to determine rate of change
757                          */
758                         if (U.viewzoom != USER_ZOOM_CONT) { // XXX store this setting as RNA prop?
759                                 vzd->lastx= event->x;
760                                 vzd->lasty= event->y;
761                         }
762                         
763                         /* apply zooming */
764                         view_zoomdrag_apply(C, op);
765                 }
766                         break;
767                         
768                 case MIDDLEMOUSE:
769                         if (event->val==0) {
770                                 /* for redo, store the overall deltas - need to respect zoom-locks here... */
771                                 if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0)
772                                         RNA_float_set(op->ptr, "deltax", vzd->dx);
773                                 else
774                                         RNA_float_set(op->ptr, "deltax", 0);
775                                         
776                                 if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0)
777                                         RNA_float_set(op->ptr, "deltay", vzd->dy);
778                                 else
779                                         RNA_float_set(op->ptr, "deltay", 0);
780                                 
781                                 /* free customdata */
782                                 view_zoomdrag_exit(C, op);
783                                 WM_cursor_restore(C->window);
784                                 
785                                 return OPERATOR_FINISHED;
786                         }
787                         break;
788         }
789
790         return OPERATOR_RUNNING_MODAL;
791 }
792
793 void ED_View2D_OT_view_zoom(wmOperatorType *ot)
794 {
795         PropertyRNA *prop;
796         
797         /* identifiers */
798         ot->name= "Zoom View";
799         ot->idname= "ED_View2D_OT_view_zoom";
800         
801         /* api callbacks */
802         ot->exec= view_zoomdrag_exec;
803         ot->invoke= view_zoomdrag_invoke;
804         ot->modal= view_zoomdrag_modal;
805         
806         /* operator is repeatable */
807         ot->flag= OPTYPE_REGISTER;
808         
809         /* rna - must keep these in sync with the other operators */
810         prop= RNA_def_property(ot->srna, "deltax", PROP_FLOAT, PROP_NONE);
811         prop= RNA_def_property(ot->srna, "deltay", PROP_FLOAT, PROP_NONE);
812 }
813
814 /* ********************************************************* */
815 /* SCROLLERS */
816
817 /*      Scrollers should behave in the following ways, when clicked on with LMB (and dragged):
818  *              1) 'Handles' on end of 'bubble' - when the axis that the scroller represents is zoomable, 
819  *                      enlarge 'cur' rect on the relevant side 
820  *              2) 'Bubble'/'bar' - just drag, and bar should move with mouse (view pans opposite)
821  *
822  *      In order to make sure this works, each operator must define the following RNA-Operator Props:
823  *              deltax, deltay  - define how much to move view by (relative to zoom-correction factor)
824  */
825
826 /* customdata for scroller-invoke data */
827 typedef struct v2dScrollerMove {
828         View2D *v2d;                    /* View2D data that this operation affects */
829         
830         short scroller;                 /* scroller that mouse is in ('h' or 'v') */
831         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?)
832         
833         float fac;                              /* view adjustment factor, based on size of region */
834         float delta;                    /* amount moved by mouse on axis of interest */
835         
836         int lastx, lasty;               /* previous mouse coordinates (in screen coordinates) for determining movement */
837 } v2dScrollerMove;
838
839
840 /* View2DScrollers is typedef'd in UI_view2d.h 
841  * This is a CUT DOWN VERSION of the 'real' version, which is defined in view2d.c, as we only need focus bubble info
842  * WARNING: the start of this struct must not change, so that it stays in sync with the 'real' version
843  *                 For now, we don't need to have a separate (internal) header for structs like this...
844  */
845 struct View2DScrollers {        
846                 /* focus bubbles */
847         int vert_min, vert_max; /* vertical scrollbar */
848         int hor_min, hor_max;   /* horizontal scrollbar */
849 };
850
851 /* quick enum for vsm->zone (scroller handles) */
852 enum {
853         SCROLLHANDLE_MIN= -1,
854         SCROLLHANDLE_BAR,
855         SCROLLHANDLE_MAX
856 } eV2DScrollerHandle_Zone;
857
858 /* ------------------------ */
859
860 /* check if mouse is within scroller handle 
861  *      - mouse                 =       relevant mouse coordinate in region space
862  *      - sc_min, sc_max        =       extents of scroller
863  *      - sh_min, sh_max        =       positions of scroller handles
864  */
865 static short mouse_in_scroller_handle(int mouse, int sc_min, int sc_max, int sh_min, int sh_max)
866 {
867         short in_min, in_max, in_view=1;
868         
869         /* firstly, check if 
870          *      - 'bubble' fills entire scroller 
871          *      - 'bubble' completely out of view on either side 
872          */
873         if ((sh_min <= sc_min) && (sh_max >= sc_max)) in_view= 0;
874         if (sh_min == sh_max) {
875                 if (sh_min <= sc_min) in_view= 0;
876                 if (sh_max >= sc_max) in_view= 0;
877         }
878         else {
879                 if (sh_max <= sc_min) in_view= 0;
880                 if (sh_min >= sc_max) in_view= 0;
881         }
882         
883         
884         if (in_view == 0) {
885                 /* handles are only activated if the mouse is within the relative quater lengths of the scroller */
886                 int qLen = (sc_max + sc_min) / 4;
887                 
888                 if (mouse >= (sc_max - qLen))
889                         return SCROLLHANDLE_MAX;
890                 else if (mouse <= qLen)
891                         return SCROLLHANDLE_MIN;
892                 else
893                         return SCROLLHANDLE_BAR;
894         }
895         
896         /* check if mouse is in or past either handle */
897         in_max= ( (mouse >= (sh_max - V2D_SCROLLER_HANDLE_SIZE)) && (mouse <= (sh_max + V2D_SCROLLER_HANDLE_SIZE)) );
898         in_min= ( (mouse <= (sh_min + V2D_SCROLLER_HANDLE_SIZE)) && (mouse >= (sh_min - V2D_SCROLLER_HANDLE_SIZE)) );
899         
900         /* check if overlap --> which means user clicked on bar, as bar is within handles region */
901         if (in_max && in_min)
902                 return SCROLLHANDLE_BAR;
903         else if (in_max)
904                 return SCROLLHANDLE_MAX;
905         else if (in_min)
906                 return SCROLLHANDLE_MIN;
907                 
908         /* unlikely to happen, though we just cover it in case */
909         return SCROLLHANDLE_BAR;
910
911
912 /* initialise customdata for scroller manipulation operator */
913 static void scroller_activate_init(bContext *C, wmOperator *op, wmEvent *event, short in_scroller)
914 {
915         v2dScrollerMove *vsm;
916         View2DScrollers *scrollers;
917         ARegion *ar= C->region;
918         View2D *v2d= &ar->v2d;
919         float mask_size;
920         int x, y;
921         
922         /* set custom-data for operator */
923         vsm= MEM_callocN(sizeof(v2dScrollerMove), "v2dScrollerMove");
924         op->customdata= vsm;
925         
926         /* set general data */
927         vsm->v2d= v2d;
928         vsm->scroller= in_scroller;
929         
930         /* store mouse-coordinates, and convert mouse/screen coordinates to region coordinates */
931         vsm->lastx = event->x;
932         vsm->lasty = event->y;
933         x= event->x - ar->winrct.xmin;
934         y= event->y - ar->winrct.ymin;
935         
936         /* 'zone' depends on where mouse is relative to bubble 
937          *      - zooming must be allowed on this axis, otherwise, default to pan
938          */
939         scrollers= UI_view2d_scrollers_calc(C, v2d, V2D_ARG_DUMMY, V2D_ARG_DUMMY, V2D_ARG_DUMMY, V2D_ARG_DUMMY);
940         if (in_scroller == 'h') {
941                 /* horizontal scroller - calculate adjustment factor first */
942                 mask_size= (float)(v2d->hor.xmax - v2d->hor.xmin);
943                 vsm->fac= (v2d->tot.xmax - v2d->tot.xmin) / mask_size;
944                 
945                 /* get 'zone' (i.e. which part of scroller is activated) */
946                 if (v2d->keepzoom & V2D_LOCKZOOM_X) {
947                         /* default to scroll, as handles not usable */
948                         vsm->zone= SCROLLHANDLE_BAR;
949                 }
950                 else {
951                         /* check which handle we're in */
952                         vsm->zone= mouse_in_scroller_handle(x, v2d->hor.xmin, v2d->hor.xmax, scrollers->hor_min, scrollers->hor_max); 
953                 }
954         }
955         else {
956                 /* vertical scroller - calculate adjustment factor first */
957                 mask_size= (float)(v2d->vert.ymax - v2d->vert.ymin);
958                 vsm->fac= (v2d->tot.ymax - v2d->tot.ymin) / mask_size;
959                 
960                 /* get 'zone' (i.e. which part of scroller is activated) */
961                 if (v2d->keepzoom & V2D_LOCKZOOM_Y) {
962                         /* default to scroll, as handles not usable */
963                         vsm->zone= SCROLLHANDLE_BAR;
964                 }
965                 else {
966                         /* check which handle we're in */
967                         vsm->zone= mouse_in_scroller_handle(y, v2d->vert.ymin, v2d->vert.ymax, scrollers->vert_min, scrollers->vert_max); 
968                 }
969         }
970         UI_view2d_scrollers_free(scrollers);
971 }
972
973 /* cleanup temp customdata  */
974 static void scroller_activate_exit(bContext *C, wmOperator *op)
975 {
976         if (op->customdata) {
977                 MEM_freeN(op->customdata);
978                 op->customdata= NULL;                           
979         }
980
981
982 /* apply transform to view (i.e. adjust 'cur' rect) */
983 static void scroller_activate_apply(bContext *C, wmOperator *op)
984 {
985         v2dScrollerMove *vsm= op->customdata;
986         View2D *v2d= vsm->v2d;
987         float temp;
988         
989         /* calculate amount to move view by */
990         temp= vsm->fac * vsm->delta;
991         
992         /* type of movement */
993         switch (vsm->zone) {
994                 case SCROLLHANDLE_MIN:
995                         /* only expand view on axis if zoom is allowed */
996                         if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X))
997                                 v2d->cur.xmin -= temp;
998                         if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y))
999                                 v2d->cur.ymin -= temp;
1000                         break;
1001                 
1002                 case SCROLLHANDLE_MAX:
1003                         /* only expand view on axis if zoom is allowed */
1004                         if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X))
1005                                 v2d->cur.xmax += temp;
1006                         if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y))
1007                                 v2d->cur.ymax += temp;
1008                         break;
1009                 
1010                 default: /* SCROLLHANDLE_BAR */
1011                         /* only move view on an axis if panning is allowed */
1012                         if ((vsm->scroller == 'h') && !(v2d->keepofs & V2D_LOCKOFS_X)) {
1013                                 v2d->cur.xmin += temp;
1014                                 v2d->cur.xmax += temp;
1015                         }
1016                         if ((vsm->scroller == 'v') && !(v2d->keepofs & V2D_LOCKOFS_Y)) {
1017                                 v2d->cur.ymin += temp;
1018                                 v2d->cur.ymax += temp;
1019                         }
1020                         break;
1021         }
1022         
1023         /* validate that view is in valid configuration after this operation */
1024         UI_view2d_curRect_validate(v2d);
1025         
1026         /* request updates to be done... */
1027         WM_event_add_notifier(C, WM_NOTE_AREA_REDRAW, 0, NULL);
1028         /* XXX: add WM_NOTE_TIME_CHANGED? */
1029 }
1030
1031 /* handle user input for scrollers - calculations of mouse-movement need to be done here, not in the apply callback! */
1032 static int scroller_activate_modal(bContext *C, wmOperator *op, wmEvent *event)
1033 {
1034         v2dScrollerMove *vsm= op->customdata;
1035         
1036         /* execute the events */
1037         switch (event->type) {
1038                 case MOUSEMOVE:
1039                 {
1040                         /* calculate new delta transform, then store mouse-coordinates for next-time */
1041                         if (vsm->zone != SCROLLHANDLE_MIN) {
1042                                 /* if using bar (i.e. 'panning') or 'max' zoom widget */
1043                                 switch (vsm->scroller) {
1044                                         case 'h': /* horizontal scroller - so only horizontal movement ('cur' moves opposite to mouse) */
1045                                                 vsm->delta= (float)(event->x - vsm->lastx);
1046                                                 break;
1047                                         case 'v': /* vertical scroller - so only vertical movement ('cur' moves opposite to mouse) */
1048                                                 vsm->delta= (float)(event->y - vsm->lasty);
1049                                                 break;
1050                                 }
1051                         }
1052                         else {
1053                                 /* using 'min' zoom widget */
1054                                 switch (vsm->scroller) {
1055                                         case 'h': /* horizontal scroller - so only horizontal movement ('cur' moves with mouse) */
1056                                                 vsm->delta= (float)(vsm->lastx - event->x);
1057                                                 break;
1058                                         case 'v': /* vertical scroller - so only vertical movement ('cur' moves with to mouse) */
1059                                                 vsm->delta= (float)(vsm->lasty - event->y);
1060                                                 break;
1061                                 }
1062                         }
1063                         
1064                         /* store previous coordinates */
1065                         vsm->lastx= event->x;
1066                         vsm->lasty= event->y;
1067                         
1068                         scroller_activate_apply(C, op);
1069                 }
1070                         break;
1071                         
1072                 case LEFTMOUSE:
1073                         if (event->val==0) {
1074                                 scroller_activate_exit(C, op);
1075                                 return OPERATOR_FINISHED;
1076                         }
1077                         break;
1078         }
1079
1080         return OPERATOR_RUNNING_MODAL;
1081 }
1082
1083
1084 /* a click (or click drag in progress) should have occurred, so check if it happened in scrollbar */
1085 static int scroller_activate_invoke(bContext *C, wmOperator *op, wmEvent *event)
1086 {
1087         View2D *v2d= NULL;
1088         short in_scroller= 0;
1089         
1090         /* firstly, check context to see if mouse is actually in region */
1091         // XXX isn't this the job of poll() callbacks which can't check events, but only context?
1092         if (C->region == NULL) 
1093                 return OPERATOR_CANCELLED;
1094         else
1095                 v2d= &C->region->v2d;
1096                 
1097         /* check if mouse in scrollbars, if they're enabled */
1098         in_scroller= mouse_in_v2d_scrollers(C, v2d, event->x, event->y);
1099         
1100         /* if in a scroller, init customdata then set modal handler which will catch mousedown to start doing useful stuff */
1101         if (in_scroller) {
1102                 v2dScrollerMove *vsm;
1103                 
1104                 /* initialise customdata */
1105                 scroller_activate_init(C, op, event, in_scroller);
1106                 vsm= (v2dScrollerMove *)op->customdata;
1107                 
1108                 /* check if zone is inappropriate (i.e. 'bar' but panning is banned), so cannot continue */
1109                 if (vsm->zone == SCROLLHANDLE_BAR) {
1110                         if ( ((vsm->scroller=='h') && (v2d->keepofs & V2D_LOCKOFS_X)) ||
1111                                  ((vsm->scroller=='v') && (v2d->keepofs & V2D_LOCKOFS_Y)) )
1112                         {
1113                                 /* free customdata initialised */
1114                                 scroller_activate_exit(C, op);
1115                                 
1116                                 /* can't catch this event for ourselves, so let it go to someone else? */
1117                                 return OPERATOR_PASS_THROUGH;
1118                         }                       
1119                 }
1120                 
1121                 /* still ok, so can add */
1122                 WM_event_add_modal_handler(C, &C->window->handlers, op);
1123                 return OPERATOR_RUNNING_MODAL;
1124         }
1125         else {
1126                 /* not in scroller, so nothing happened... (pass through let's something else catch event) */
1127                 return OPERATOR_PASS_THROUGH;
1128         }
1129 }
1130
1131 /* LMB-Drag in Scrollers - not repeatable operator! */
1132 void ED_View2D_OT_scroller_activate(wmOperatorType *ot)
1133 {
1134         /* identifiers */
1135         ot->name= "Scroller Activate";
1136         ot->idname= "ED_View2D_OT_scroller_activate";
1137         
1138         /* api callbacks */
1139         ot->invoke= scroller_activate_invoke;
1140         ot->modal= scroller_activate_modal;
1141 }
1142  
1143 /* ********************************************************* */
1144 /* Registration */
1145
1146 void ui_view2d_operatortypes(void)
1147 {
1148         WM_operatortype_append(ED_View2D_OT_view_pan);
1149         
1150         WM_operatortype_append(ED_View2D_OT_view_scrollleft);
1151         WM_operatortype_append(ED_View2D_OT_view_scrollright);
1152         WM_operatortype_append(ED_View2D_OT_view_scrollup);
1153         WM_operatortype_append(ED_View2D_OT_view_scrolldown);
1154         
1155         WM_operatortype_append(ED_View2D_OT_view_zoomin);
1156         WM_operatortype_append(ED_View2D_OT_view_zoomout);
1157         
1158         WM_operatortype_append(ED_View2D_OT_view_zoom);
1159         
1160         WM_operatortype_append(ED_View2D_OT_scroller_activate);
1161 }
1162
1163 void UI_view2d_keymap(wmWindowManager *wm)
1164 {
1165         ListBase *keymap= WM_keymap_listbase(wm, "View2D", 0, 0);
1166         
1167         /* pan/scroll */
1168         WM_keymap_add_item(keymap, "ED_View2D_OT_view_pan", MIDDLEMOUSE, KM_PRESS, 0, 0);
1169         
1170         WM_keymap_add_item(keymap, "ED_View2D_OT_view_rightscroll", WHEELDOWNMOUSE, KM_ANY, KM_CTRL, 0);
1171         WM_keymap_add_item(keymap, "ED_View2D_OT_view_leftscroll", WHEELUPMOUSE, KM_ANY, KM_CTRL, 0);
1172         
1173         WM_keymap_add_item(keymap, "ED_View2D_OT_view_downscroll", WHEELDOWNMOUSE, KM_ANY, KM_SHIFT, 0);
1174         WM_keymap_add_item(keymap, "ED_View2D_OT_view_upscroll", WHEELUPMOUSE, KM_ANY, KM_SHIFT, 0);
1175         
1176         /* zoom - single step */
1177         WM_keymap_add_item(keymap, "ED_View2D_OT_view_zoomout", WHEELUPMOUSE, KM_ANY, 0, 0);
1178         WM_keymap_add_item(keymap, "ED_View2D_OT_view_zoomin", WHEELDOWNMOUSE, KM_ANY, 0, 0);
1179         WM_keymap_add_item(keymap, "ED_View2D_OT_view_zoomout", PADMINUS, KM_PRESS, 0, 0);
1180         WM_keymap_add_item(keymap, "ED_View2D_OT_view_zoomin", PADPLUSKEY, KM_PRESS, 0, 0);
1181         
1182         /* zoom - drag */
1183         WM_keymap_add_item(keymap, "ED_View2D_OT_view_zoom", MIDDLEMOUSE, KM_PRESS, KM_CTRL, 0);
1184         
1185         /* scrollers */
1186         WM_keymap_add_item(keymap, "ED_View2D_OT_scroller_activate", LEFTMOUSE, KM_PRESS, 0, 0);
1187 }
1188