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