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