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