diff options
Diffstat (limited to 'source/blender/editors/interface')
20 files changed, 25975 insertions, 0 deletions
diff --git a/source/blender/editors/interface/Makefile b/source/blender/editors/interface/Makefile new file mode 100644 index 00000000000..dfc8187de49 --- /dev/null +++ b/source/blender/editors/interface/Makefile @@ -0,0 +1,59 @@ +# +# $Id: Makefile 14 2002-10-13 15:57:19Z hans $ +# +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# The Original Code is Copyright (C) 2007 Blender Foundation +# All rights reserved. +# +# The Original Code is: all of this file. +# +# Contributor(s): none yet. +# +# ***** END GPL LICENSE BLOCK ***** +# +# Makes module object directory and bounces make to subdirectories. + +LIBNAME = ed_interface +DIR = $(OCGDIR)/blender/$(LIBNAME) + +include nan_compile.mk + +CFLAGS += $(LEVEL_1_C_WARNINGS) + +CPPFLAGS += -I$(NAN_GLEW)/include +CPPFLAGS += -I$(OPENGL_HEADERS) + +CPPFLAGS += -I$(NAN_GUARDEDALLOC)/include + +CPPFLAGS += -I../../windowmanager +CPPFLAGS += -I../../blenkernel +CPPFLAGS += -I../../blenloader +CPPFLAGS += -I../../blenlib +CPPFLAGS += -I../../makesdna +CPPFLAGS += -I../../makesrna +CPPFLAGS += -I../../imbuf +CPPFLAGS += -I../../blenfont + +# own include + +CPPFLAGS += -I../include + +ifeq ($(INTERNATIONAL), true) + CPPFLAGS += -DINTERNATIONAL +endif + diff --git a/source/blender/editors/interface/SConscript b/source/blender/editors/interface/SConscript new file mode 100644 index 00000000000..bac3742c12f --- /dev/null +++ b/source/blender/editors/interface/SConscript @@ -0,0 +1,18 @@ +#!/usr/bin/python +Import ('env') + +sources = env.Glob('*.c') + +for source in env.Glob('*_api.c'): + sources.remove(source) + +incs = '../include ../../blenlib ../../blenfont ../../blenkernel ../../makesdna ../../imbuf' +incs += ' ../../makesrna ../../windowmanager #/intern/guardedalloc' +incs += ' #/extern/glew/include' + +defs = [] + +if env['WITH_BF_INTERNATIONAL']: + defs.append('INTERNATIONAL') + +env.BlenderLib ( 'bf_editors_interface', sources, Split(incs), Split(defs), libtype=['core'], priority=[110] ) diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c new file mode 100644 index 00000000000..bef01b5a454 --- /dev/null +++ b/source/blender/editors/interface/interface.c @@ -0,0 +1,3137 @@ +/** + * $Id: interface.c 16882 2008-10-02 12:29:45Z ton $ + * + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + * + * Contributor(s): Blender Foundation 2002-2008, full recode. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include <float.h> +#include <math.h> +#include <string.h> + +#include "MEM_guardedalloc.h" + +#include "DNA_ID.h" +#include "DNA_listBase.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" +#include "DNA_texture_types.h" +#include "DNA_userdef_types.h" + +#include "BLI_arithb.h" +#include "BLI_blenlib.h" +#include "BLI_dynstr.h" + +#include "BKE_context.h" +#include "BKE_idprop.h" +#include "BKE_library.h" +#include "BKE_screen.h" +#include "BKE_texture.h" +#include "BKE_utildefines.h" + +#include "BIF_gl.h" +#include "BIF_glutil.h" + +#include "BLF_api.h" + +#include "UI_interface.h" + +#include "ED_screen.h" + +#include "WM_api.h" +#include "WM_types.h" +#include "wm_subwindow.h" +#include "wm_window.h" + +#include "RNA_access.h" +#include "RNA_types.h" + +#include "interface_intern.h" + +#define MENU_WIDTH 120 +#define MENU_ITEM_HEIGHT 20 +#define MENU_SEP_HEIGHT 6 + +/* + * a full doc with API notes can be found in bf-blender/blender/doc/interface_API.txt + * + * uiBlahBlah() external function + * ui_blah_blah() internal function + */ + +static void ui_free_but(const bContext *C, uiBut *but); +static void ui_rna_ID_autocomplete(bContext *C, char *str, void *arg_but); + +/* ************* translation ************** */ + +int ui_translate_buttons() +{ + return (U.transopts & USER_TR_BUTTONS); +} + +int ui_translate_menus() +{ + return (U.transopts & USER_TR_MENUS); +} + +int ui_translate_tooltips() +{ + return (U.transopts & USER_TR_TOOLTIPS); +} + +/* ************* window matrix ************** */ + +void ui_block_to_window_fl(const ARegion *ar, uiBlock *block, float *x, float *y) +{ + float gx, gy; + int sx, sy, getsizex, getsizey; + + getsizex= ar->winrct.xmax-ar->winrct.xmin+1; + getsizey= ar->winrct.ymax-ar->winrct.ymin+1; + sx= ar->winrct.xmin; + sy= ar->winrct.ymin; + + gx= *x; + gy= *y; + + if(block->panel) { + gx += block->panel->ofsx; + gy += block->panel->ofsy; + } + + *x= ((float)sx) + ((float)getsizex)*(0.5+ 0.5*(gx*block->winmat[0][0]+ gy*block->winmat[1][0]+ block->winmat[3][0])); + *y= ((float)sy) + ((float)getsizey)*(0.5+ 0.5*(gx*block->winmat[0][1]+ gy*block->winmat[1][1]+ block->winmat[3][1])); +} + +void ui_block_to_window(const ARegion *ar, uiBlock *block, int *x, int *y) +{ + float fx, fy; + + fx= *x; + fy= *y; + + ui_block_to_window_fl(ar, block, &fx, &fy); + + *x= (int)(fx+0.5f); + *y= (int)(fy+0.5f); +} + +void ui_block_to_window_rct(const ARegion *ar, uiBlock *block, rctf *graph, rcti *winr) +{ + rctf tmpr; + + tmpr= *graph; + ui_block_to_window_fl(ar, block, &tmpr.xmin, &tmpr.ymin); + ui_block_to_window_fl(ar, block, &tmpr.xmax, &tmpr.ymax); + + winr->xmin= tmpr.xmin; + winr->ymin= tmpr.ymin; + winr->xmax= tmpr.xmax; + winr->ymax= tmpr.ymax; +} + +void ui_window_to_block_fl(const ARegion *ar, uiBlock *block, float *x, float *y) /* for mouse cursor */ +{ + float a, b, c, d, e, f, px, py; + int sx, sy, getsizex, getsizey; + + getsizex= ar->winrct.xmax-ar->winrct.xmin+1; + getsizey= ar->winrct.ymax-ar->winrct.ymin+1; + sx= ar->winrct.xmin; + sy= ar->winrct.ymin; + + a= .5*((float)getsizex)*block->winmat[0][0]; + b= .5*((float)getsizex)*block->winmat[1][0]; + c= .5*((float)getsizex)*(1.0+block->winmat[3][0]); + + d= .5*((float)getsizey)*block->winmat[0][1]; + e= .5*((float)getsizey)*block->winmat[1][1]; + f= .5*((float)getsizey)*(1.0+block->winmat[3][1]); + + px= *x - sx; + py= *y - sy; + + *y= (a*(py-f) + d*(c-px))/(a*e-d*b); + *x= (px- b*(*y)- c)/a; + + if(block->panel) { + *x -= block->panel->ofsx; + *y -= block->panel->ofsy; + } +} + +void ui_window_to_block(const ARegion *ar, uiBlock *block, int *x, int *y) +{ + float fx, fy; + + fx= *x; + fy= *y; + + ui_window_to_block_fl(ar, block, &fx, &fy); + + *x= (int)(fx+0.5f); + *y= (int)(fy+0.5f); +} + +void ui_window_to_region(const ARegion *ar, int *x, int *y) +{ + *x-= ar->winrct.xmin; + *y-= ar->winrct.ymin; +} + +/* ******************* block calc ************************* */ + +void ui_block_translate(uiBlock *block, int x, int y) +{ + uiBut *bt; + + for(bt= block->buttons.first; bt; bt=bt->next) { + bt->x1 += x; + bt->y1 += y; + bt->x2 += x; + bt->y2 += y; + } + + block->minx += x; + block->miny += y; + block->maxx += x; + block->maxy += y; +} + +static void ui_text_bounds_block(uiBlock *block, float offset) +{ + uiStyle *style= U.uistyles.first; // XXX pass on as arg + uiBut *bt; + int i = 0, j, x1addval= offset, nextcol; + + uiStyleFontSet(&style->widget); + + for(bt= block->buttons.first; bt; bt= bt->next) { + if(bt->type!=SEPR) { + //int transopts= ui_translate_buttons(); + //if(bt->type==TEX || bt->type==IDPOIN) transopts= 0; + + j= BLF_width(bt->drawstr); + + if(j > i) i = j; + } + } + + /* cope with multi collumns */ + bt= block->buttons.first; + while(bt) { + if(bt->next && bt->x1 < bt->next->x1) + nextcol= 1; + else nextcol= 0; + + bt->x1 = x1addval; + bt->x2 = bt->x1 + i + block->bounds; + + ui_check_but(bt); // clips text again + + if(nextcol) + x1addval+= i + block->bounds; + + bt= bt->next; + } +} + +void ui_bounds_block(uiBlock *block) +{ + uiBut *bt; + int xof; + + if(block->buttons.first==NULL) { + if(block->panel) { + block->minx= 0.0; block->maxx= block->panel->sizex; + block->miny= 0.0; block->maxy= block->panel->sizey; + } + } + else { + + block->minx= block->miny= 10000; + block->maxx= block->maxy= -10000; + + bt= block->buttons.first; + while(bt) { + if(bt->x1 < block->minx) block->minx= bt->x1; + if(bt->y1 < block->miny) block->miny= bt->y1; + + if(bt->x2 > block->maxx) block->maxx= bt->x2; + if(bt->y2 > block->maxy) block->maxy= bt->y2; + + bt= bt->next; + } + + block->minx -= block->bounds; + block->miny -= block->bounds; + block->maxx += block->bounds; + block->maxy += block->bounds; + } + + /* hardcoded exception... but that one is annoying with larger safety */ + bt= block->buttons.first; + if(bt && strncmp(bt->str, "ERROR", 5)==0) xof= 10; + else xof= 40; + + block->safety.xmin= block->minx-xof; + block->safety.ymin= block->miny-xof; + block->safety.xmax= block->maxx+xof; + block->safety.ymax= block->maxy+xof; +} + +static void ui_popup_bounds_block(const bContext *C, uiBlock *block, int menu) +{ + wmWindow *window= CTX_wm_window(C); + int startx, starty, endx, endy, width, height; + int oldbounds, mx, my, xmax, ymax; + + oldbounds= block->bounds; + + /* compute mouse position with user defined offset */ + ui_bounds_block(block); + mx= window->eventstate->x + block->minx + block->mx; + my= window->eventstate->y + block->miny + block->my; + + wm_window_get_size(window, &xmax, &ymax); + + /* first we ensure wide enough text bounds */ + if(menu) { + if(block->flag & UI_BLOCK_LOOP) { + block->bounds= 50; + ui_text_bounds_block(block, block->minx); + } + } + + /* next we recompute bounds */ + block->bounds= oldbounds; + ui_bounds_block(block); + + /* and we adjust the position to fit within window */ + width= block->maxx - block->minx; + height= block->maxy - block->miny; + + startx= mx-(0.8*(width)); + starty= my; + + if(startx<10) + startx= 10; + if(starty<10) + starty= 10; + + endx= startx+width; + endy= starty+height; + + if(endx>xmax) { + endx= xmax-10; + startx= endx-width; + } + if(endy>ymax-20) { + endy= ymax-20; + starty= endy-height; + } + + ui_block_translate(block, startx - block->minx, starty - block->miny); + + /* now recompute bounds and safety */ + ui_bounds_block(block); +} + +/* used for various cases */ +void uiBoundsBlock(uiBlock *block, int addval) +{ + if(block==NULL) + return; + + block->bounds= addval; + block->dobounds= 1; +} + +/* used for pulldowns */ +void uiTextBoundsBlock(uiBlock *block, int addval) +{ + block->bounds= addval; + block->dobounds= 2; +} + +/* used for block popups */ +void uiPopupBoundsBlock(uiBlock *block, int addval, int mx, int my) +{ + block->bounds= addval; + block->dobounds= 3; + block->mx= mx; + block->my= my; +} + +/* used for menu popups */ +void uiMenuPopupBoundsBlock(uiBlock *block, int addval, int mx, int my) +{ + block->bounds= addval; + block->dobounds= 4; + block->mx= mx; + block->my= my; +} + +/* ************** LINK LINE DRAWING ************* */ + +/* link line drawing is not part of buttons or theme.. so we stick with it here */ + +static void ui_draw_linkline(uiBut *but, uiLinkLine *line) +{ + float vec1[2], vec2[2]; + + if(line->from==NULL || line->to==NULL) return; + + vec1[0]= (line->from->x1+line->from->x2)/2.0; + vec1[1]= (line->from->y1+line->from->y2)/2.0; + vec2[0]= (line->to->x1+line->to->x2)/2.0; + vec2[1]= (line->to->y1+line->to->y2)/2.0; + + if(line->flag & UI_SELECT) glColor3ub(100,100,100); + else glColor3ub(0,0,0); + fdrawline(vec1[0], vec1[1], vec2[0], vec2[1]); +} + +static void ui_draw_links(uiBlock *block) +{ + uiBut *but; + uiLinkLine *line; + + but= block->buttons.first; + while(but) { + if(but->type==LINK && but->link) { + line= but->link->lines.first; + while(line) { + ui_draw_linkline(but, line); + line= line->next; + } + } + but= but->next; + } +} + +/* ************** BLOCK ENDING FUNCTION ************* */ + +static int ui_but_equals_old(uiBut *but, uiBut *oldbut) +{ + /* various properties are being compared here, hopfully sufficient + * to catch all cases, but it is simple to add more checks later */ + if(but->retval != oldbut->retval) return 0; + if(but->rnapoin.data != oldbut->rnapoin.data) return 0; + if(but->rnaprop != oldbut->rnaprop) + if(but->rnaindex != oldbut->rnaindex) return 0; + if(but->func != oldbut->func) return 0; + if(but->funcN != oldbut->funcN) return 0; + if(oldbut->func_arg1 != oldbut && but->func_arg1 != oldbut->func_arg1) return 0; + if(oldbut->func_arg2 != oldbut && but->func_arg2 != oldbut->func_arg2) return 0; + if(!but->funcN && ((but->poin != oldbut->poin && (uiBut*)oldbut->poin != oldbut) || but->pointype != oldbut->pointype)) return 0; + + return 1; +} + +static int ui_but_update_from_old_block(const bContext *C, uiBlock *block, uiBut *but) +{ + uiBlock *oldblock; + uiBut *oldbut; + int found= 0; + + oldblock= block->oldblock; + if(!oldblock) + return found; + + for(oldbut=oldblock->buttons.first; oldbut; oldbut=oldbut->next) { + if(ui_but_equals_old(oldbut, but)) { + if(oldbut->active) { + but->flag= oldbut->flag; + but->active= oldbut->active; + but->pos= oldbut->pos; + but->editstr= oldbut->editstr; + but->editval= oldbut->editval; + but->editvec= oldbut->editvec; + but->editcoba= oldbut->editcoba; + but->editcumap= oldbut->editcumap; + but->selsta= oldbut->selsta; + but->selend= oldbut->selend; + but->softmin= oldbut->softmin; + but->softmax= oldbut->softmax; + found= 1; + + oldbut->active= NULL; + } + + /* ensures one button can get activated, and in case the buttons + * draw are the same this gives O(1) lookup for each button */ + BLI_remlink(&oldblock->buttons, oldbut); + ui_free_but(C, oldbut); + + break; + } + } + + return found; +} + +void ui_menu_block_set_keymaps(const bContext *C, uiBlock *block) +{ + uiBut *but; + IDProperty *prop; + char buf[512], *butstr; + + /* only do it before bounding */ + if(block->minx != block->maxx) + return; + + for(but=block->buttons.first; but; but=but->next) { + if(but->optype) { + prop= (but->opptr)? but->opptr->data: NULL; + + if(WM_key_event_operator_string(C, but->optype->idname, but->opcontext, prop, buf, sizeof(buf))) { + butstr= MEM_mallocN(strlen(but->str)+strlen(buf)+2, "menu_block_set_keymaps"); + strcpy(butstr, but->str); + strcat(butstr, "|"); + strcat(butstr, buf); + + but->str= but->strdata; + BLI_strncpy(but->str, butstr, sizeof(but->strdata)); + MEM_freeN(butstr); + + ui_check_but(but); + } + } + } +} + +void uiEndBlock(const bContext *C, uiBlock *block) +{ + uiBut *but; + Scene *scene= CTX_data_scene(C); + + /* inherit flags from 'old' buttons that was drawn here previous, based + * on matching buttons, we need this to make button event handling non + * blocking, while still alowing buttons to be remade each redraw as it + * is expected by blender code */ + for(but=block->buttons.first; but; but=but->next) { + if(ui_but_update_from_old_block(C, block, but)) + ui_check_but(but); + + /* temp? Proper check for greying out */ + if(but->optype) { + wmOperatorType *ot= but->optype; + + if(but->context) + CTX_store_set((bContext*)C, but->context); + + if(ot==NULL || (ot->poll && ot->poll((bContext *)C)==0)) { + but->flag |= UI_BUT_DISABLED; + but->lock = 1; + } + + if(but->context) + CTX_store_set((bContext*)C, NULL); + } + + /* only update soft range while not editing */ + if(but->rnaprop && !(but->editval || but->editstr || but->editvec)) + ui_set_but_soft_range(but, ui_get_but_val(but)); + + ui_but_anim_flag(but, (scene)? scene->r.cfra: 0.0f); + } + + if(block->oldblock) { + block->auto_open= block->oldblock->auto_open; + block->auto_open_last= block->oldblock->auto_open_last; + block->tooltipdisabled= block->oldblock->tooltipdisabled; + + block->oldblock= NULL; + } + + /* handle pending stuff */ + if(block->layouts.first) uiBlockLayoutResolve(C, block, NULL, NULL); + ui_block_do_align(block); + if(block->flag & UI_BLOCK_LOOP) ui_menu_block_set_keymaps(C, block); + + /* after keymaps! */ + if(block->dobounds == 1) ui_bounds_block(block); + else if(block->dobounds == 2) ui_text_bounds_block(block, 0.0f); + else if(block->dobounds) ui_popup_bounds_block(C, block, (block->dobounds == 4)); + + if(block->minx==0.0 && block->maxx==0.0) uiBoundsBlock(block, 0); + if(block->flag & UI_BUT_ALIGN) uiBlockEndAlign(block); + + block->endblock= 1; +} + +/* ************** BLOCK DRAWING FUNCTION ************* */ + +void ui_fontscale(short *points, float aspect) +{ + if(aspect < 0.9f || aspect > 1.1f) { + float pointsf= *points; + + /* for some reason scaling fonts goes too fast compared to widget size */ + aspect= sqrt(aspect); + pointsf /= aspect; + + if(aspect > 1.0) + *points= ceil(pointsf); + else + *points= floor(pointsf); + } +} + +/* project button or block (but==NULL) to pixels in regionspace */ +static void ui_but_to_pixelrect(rcti *rect, const ARegion *ar, uiBlock *block, uiBut *but) +{ + float gx, gy; + float getsizex, getsizey; + + getsizex= ar->winx; + getsizey= ar->winy; + + gx= (but?but->x1:block->minx) + (block->panel?block->panel->ofsx:0.0f); + gy= (but?but->y1:block->miny) + (block->panel?block->panel->ofsy:0.0f); + + rect->xmin= floor(getsizex*(0.5+ 0.5*(gx*block->winmat[0][0]+ gy*block->winmat[1][0]+ block->winmat[3][0]))); + rect->ymin= floor(getsizey*(0.5+ 0.5*(gx*block->winmat[0][1]+ gy*block->winmat[1][1]+ block->winmat[3][1]))); + + gx= (but?but->x2:block->maxx) + (block->panel?block->panel->ofsx:0.0f); + gy= (but?but->y2:block->maxy) + (block->panel?block->panel->ofsy:0.0f); + + rect->xmax= floor(getsizex*(0.5+ 0.5*(gx*block->winmat[0][0]+ gy*block->winmat[1][0]+ block->winmat[3][0]))); + rect->ymax= floor(getsizey*(0.5+ 0.5*(gx*block->winmat[0][1]+ gy*block->winmat[1][1]+ block->winmat[3][1]))); + +} + +/* uses local copy of style, to scale things down, and allow widgets to change stuff */ +void uiDrawBlock(const bContext *C, uiBlock *block) +{ + uiStyle style= *((uiStyle *)U.uistyles.first); // XXX pass on as arg + ARegion *ar; + uiBut *but; + rcti rect; + + /* get menu region or area region */ + ar= CTX_wm_menu(C); + if(!ar) + ar= CTX_wm_region(C); + + if(!block->endblock) + uiEndBlock(C, block); + + /* we set this only once */ + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + /* scale fonts */ + ui_fontscale(&style.paneltitle.points, block->aspect); + ui_fontscale(&style.grouplabel.points, block->aspect); + ui_fontscale(&style.widgetlabel.points, block->aspect); + ui_fontscale(&style.widget.points, block->aspect); + + /* scale block min/max to rect */ + ui_but_to_pixelrect(&rect, ar, block, NULL); + + /* pixel space for AA widgets */ + wmPushMatrix(); + wmLoadIdentity(); + + wmOrtho2(-0.01f, ar->winx-0.01f, -0.01f, ar->winy-0.01f); + + /* back */ + if(block->flag & UI_BLOCK_LOOP) + ui_draw_menu_back(&style, block, &rect); + else if(block->panel) + ui_draw_aligned_panel(ar, &style, block, &rect); + + /* widgets */ + for(but= block->buttons.first; but; but= but->next) { + ui_but_to_pixelrect(&rect, ar, block, but); + ui_draw_but(C, ar, &style, but, &rect); + } + + /* restore matrix */ + wmPopMatrix(); + + ui_draw_links(block); +} + +/* ************* EVENTS ************* */ + +static void ui_is_but_sel(uiBut *but) +{ + double value; + int lvalue; + short push=0, true=1; + + value= ui_get_but_val(but); + + if(ELEM3(but->type, TOGN, ICONTOGN, OPTIONN)) true= 0; + + if( but->bit ) { + lvalue= (int)value; + if( BTST(lvalue, (but->bitnr)) ) push= true; + else push= !true; + } + else { + switch(but->type) { + case BUT: + push= 2; + break; + case KEYEVT: + if (value==-1) push= 1; + break; + case TOGBUT: + case TOG: + case TOGR: + case TOG3: + case BUT_TOGDUAL: + case ICONTOG: + case OPTION: + if(value!=but->hardmin) push= 1; + break; + case ICONTOGN: + case TOGN: + case OPTIONN: + if(value==0.0) push= 1; + break; + case ROW: + if(value == but->hardmax) push= 1; + break; + case COL: + push= 1; + break; + default: + push= 2; + break; + } + } + + if(push==2); + else if(push==1) but->flag |= UI_SELECT; + else but->flag &= ~UI_SELECT; +} + +/* XXX 2.50 no links supported yet */ + +#if 0 +static uiBut *ui_get_valid_link_button(uiBlock *block, uiBut *but, short *mval) +{ + uiBut *bt; + + /* find button to link to */ + for (bt= block->buttons.first; bt; bt= bt->next) + if(bt!=but && uibut_contains_pt(bt, mval)) + break; + + if (bt) { + if (but->type==LINK && bt->type==INLINK) { + if( but->link->tocode == (int)bt->min ) { + return bt; + } + } + else if(but->type==INLINK && bt->type==LINK) { + if( bt->link->tocode == (int)but->hardmin ) { + return bt; + } + } + } + + return NULL; +} + +static int ui_is_a_link(uiBut *from, uiBut *to) +{ + uiLinkLine *line; + uiLink *link; + + link= from->link; + if(link) { + line= link->lines.first; + while(line) { + if(line->from==from && line->to==to) return 1; + line= line->next; + } + } + return 0; +} + +static uiBut *ui_find_inlink(uiBlock *block, void *poin) +{ + uiBut *but; + + but= block->buttons.first; + while(but) { + if(but->type==INLINK) { + if(but->poin == poin) return but; + } + but= but->next; + } + return NULL; +} + +static void ui_add_link_line(ListBase *listb, uiBut *but, uiBut *bt) +{ + uiLinkLine *line; + + line= MEM_callocN(sizeof(uiLinkLine), "linkline"); + BLI_addtail(listb, line); + line->from= but; + line->to= bt; +} + +uiBut *uiFindInlink(uiBlock *block, void *poin) +{ + return ui_find_inlink(block, poin); +} + +void uiComposeLinks(uiBlock *block) +{ + uiBut *but, *bt; + uiLink *link; + void ***ppoin; + int a; + + but= block->buttons.first; + while(but) { + if(but->type==LINK) { + link= but->link; + + /* for all pointers in the array */ + if(link) { + if(link->ppoin) { + ppoin= link->ppoin; + for(a=0; a < *(link->totlink); a++) { + bt= ui_find_inlink(block, (*ppoin)[a] ); + if(bt) { + ui_add_link_line(&link->lines, but, bt); + } + } + } + else if(link->poin) { + bt= ui_find_inlink(block, *(link->poin) ); + if(bt) { + ui_add_link_line(&link->lines, but, bt); + } + } + } + } + but= but->next; + } +} + +static void ui_add_link(uiBut *from, uiBut *to) +{ + /* in 'from' we have to add a link to 'to' */ + uiLink *link; + void **oldppoin; + int a; + + if(ui_is_a_link(from, to)) { + printf("already exists\n"); + return; + } + + link= from->link; + + /* are there more pointers allowed? */ + if(link->ppoin) { + oldppoin= *(link->ppoin); + + (*(link->totlink))++; + *(link->ppoin)= MEM_callocN( *(link->totlink)*sizeof(void *), "new link"); + + for(a=0; a< (*(link->totlink))-1; a++) { + (*(link->ppoin))[a]= oldppoin[a]; + } + (*(link->ppoin))[a]= to->poin; + + if(oldppoin) MEM_freeN(oldppoin); + } + else { + *(link->poin)= to->poin; + } + +} + +static int ui_do_but_LINK(uiBlock *block, uiBut *but) +{ + /* + * This button only visualizes, the dobutton mode + * can add a new link, but then the whole system + * should be redrawn/initialized. + * + */ + uiBut *bt=0, *bto=NULL; + short sval[2], mval[2], mvalo[2], first= 1; + + uiGetMouse(curarea->win, sval); + mvalo[0]= sval[0]; + mvalo[1]= sval[1]; + + while (get_mbut() & L_MOUSE) { + uiGetMouse(curarea->win, mval); + + if(mval[0]!=mvalo[0] || mval[1]!=mvalo[1] || first) { + /* clear completely, because of drawbuttons */ + bt= ui_get_valid_link_button(block, but, mval); + if(bt) { + bt->flag |= UI_ACTIVE; + ui_draw_but(ar, bt); + } + if(bto && bto!=bt) { + bto->flag &= ~UI_ACTIVE; + ui_draw_but(ar, bto); + } + bto= bt; + + if (!first) { + glutil_draw_front_xor_line(sval[0], sval[1], mvalo[0], mvalo[1]); + } + glutil_draw_front_xor_line(sval[0], sval[1], mval[0], mval[1]); + + mvalo[0]= mval[0]; + mvalo[1]= mval[1]; + + first= 0; + } + else UI_wait_for_statechange(); + } + + if (!first) { + glutil_draw_front_xor_line(sval[0], sval[1], mvalo[0], mvalo[1]); + } + + if(bt) { + if(but->type==LINK) ui_add_link(but, bt); + else ui_add_link(bt, but); + + scrarea_queue_winredraw(curarea); + } + + return 0; +} +#endif + +/* ************************************************ */ + +void uiBlockSetButLock(uiBlock *block, int val, char *lockstr) +{ + if(val) { + block->lock |= val; + block->lockstr= lockstr; + } +} + +void uiBlockClearButLock(uiBlock *block) +{ + block->lock= 0; + block->lockstr= NULL; +} + +/* *************************************************************** */ + + +/* XXX 2.50 no links supported yet */ +#if 0 +static void ui_delete_active_linkline(uiBlock *block) +{ + uiBut *but; + uiLink *link; + uiLinkLine *line, *nline; + int a, b; + + but= block->buttons.first; + while(but) { + if(but->type==LINK && but->link) { + line= but->link->lines.first; + while(line) { + + nline= line->next; + + if(line->flag & UI_SELECT) { + BLI_remlink(&but->link->lines, line); + + link= line->from->link; + + /* are there more pointers allowed? */ + if(link->ppoin) { + + if(*(link->totlink)==1) { + *(link->totlink)= 0; + MEM_freeN(*(link->ppoin)); + *(link->ppoin)= NULL; + } + else { + b= 0; + for(a=0; a< (*(link->totlink)); a++) { + + if( (*(link->ppoin))[a] != line->to->poin ) { + (*(link->ppoin))[b]= (*(link->ppoin))[a]; + b++; + } + } + (*(link->totlink))--; + } + } + else { + *(link->poin)= NULL; + } + + MEM_freeN(line); + } + line= nline; + } + } + but= but->next; + } + + /* temporal! these buttons can be everywhere... */ + allqueue(REDRAWBUTSLOGIC, 0); +} + +static void ui_do_active_linklines(uiBlock *block, short *mval) +{ + uiBut *but; + uiLinkLine *line, *act= NULL; + float mindist= 12.0, fac, v1[2], v2[2], v3[3]; + int foundone= 0; + + if(mval) { + v1[0]= mval[0]; + v1[1]= mval[1]; + + /* find a line close to the mouse */ + but= block->buttons.first; + while(but) { + if(but->type==LINK && but->link) { + foundone= 1; + line= but->link->lines.first; + while(line) { + v2[0]= line->from->x2; + v2[1]= (line->from->y1+line->from->y2)/2.0; + v3[0]= line->to->x1; + v3[1]= (line->to->y1+line->to->y2)/2.0; + + fac= PdistVL2Dfl(v1, v2, v3); + if(fac < mindist) { + mindist= fac; + act= line; + } + line= line->next; + } + } + but= but->next; + } + } + + /* check for a 'found one' to prevent going to 'frontbuffer' mode. + this slows done gfx quite some, and at OSX the 'finish' forces a swapbuffer */ + if(foundone) { + glDrawBuffer(GL_FRONT); + + /* draw */ + but= block->buttons.first; + while(but) { + if(but->type==LINK && but->link) { + line= but->link->lines.first; + while(line) { + if(line==act) { + if((line->flag & UI_SELECT)==0) { + line->flag |= UI_SELECT; + ui_draw_linkline(but, line); + } + } + else if(line->flag & UI_SELECT) { + line->flag &= ~UI_SELECT; + ui_draw_linkline(but, line); + } + line= line->next; + } + } + but= but->next; + } + bglFlush(); + glDrawBuffer(GL_BACK); + } +} +#endif + +/* ******************************************************* */ + +/* XXX 2.50 no screendump supported yet */ + +#if 0 +/* nasty but safe way to store screendump rect */ +static int scr_x=0, scr_y=0, scr_sizex=0, scr_sizey=0; + +static void ui_set_screendump_bbox(uiBlock *block) +{ + if(block) { + scr_x= block->minx; + scr_y= block->miny; + scr_sizex= block->maxx - block->minx; + scr_sizey= block->maxy - block->miny; + } + else { + scr_sizex= scr_sizey= 0; + } +} + +/* used for making screenshots for menus, called in screendump.c */ +int uiIsMenu(int *x, int *y, int *sizex, int *sizey) +{ + if(scr_sizex!=0 && scr_sizey!=0) { + *x= scr_x; + *y= scr_y; + *sizex= scr_sizex; + *sizey= scr_sizey; + return 1; + } + + return 0; +} +#endif + +/* *********************** data get/set *********************** + * this either works with the pointed to data, or can work with + * an edit override pointer while dragging for example */ + +/* for buttons pointing to color for example */ +void ui_get_but_vectorf(uiBut *but, float *vec) +{ + PropertyRNA *prop; + int a, tot; + + if(but->editvec) { + VECCOPY(vec, but->editvec); + return; + } + + if(but->rnaprop) { + prop= but->rnaprop; + + vec[0]= vec[1]= vec[2]= 0.0f; + + if(RNA_property_type(prop) == PROP_FLOAT) { + tot= RNA_property_array_length(prop); + tot= MIN2(tot, 3); + + for(a=0; a<tot; a++) + vec[a]= RNA_property_float_get_index(&but->rnapoin, prop, a); + } + } + else if(but->pointype == CHA) { + char *cp= (char *)but->poin; + vec[0]= ((float)cp[0])/255.0; + vec[1]= ((float)cp[1])/255.0; + vec[2]= ((float)cp[2])/255.0; + } + else if(but->pointype == FLO) { + float *fp= (float *)but->poin; + VECCOPY(vec, fp); + } +} + +/* for buttons pointing to color for example */ +void ui_set_but_vectorf(uiBut *but, float *vec) +{ + PropertyRNA *prop; + int a, tot; + + if(but->editvec) { + VECCOPY(but->editvec, vec); + return; + } + + if(but->rnaprop) { + prop= but->rnaprop; + + if(RNA_property_type(prop) == PROP_FLOAT) { + tot= RNA_property_array_length(prop); + tot= MIN2(tot, 3); + + for(a=0; a<tot; a++) + RNA_property_float_set_index(&but->rnapoin, prop, a, vec[a]); + } + } + else if(but->pointype == CHA) { + char *cp= (char *)but->poin; + cp[0]= (char)(0.5 +vec[0]*255.0); + cp[1]= (char)(0.5 +vec[1]*255.0); + cp[2]= (char)(0.5 +vec[2]*255.0); + } + else if(but->pointype == FLO) { + float *fp= (float *)but->poin; + VECCOPY(fp, vec); + } +} + +int ui_is_but_float(uiBut *but) +{ + if(but->pointype==FLO && but->poin) + return 1; + + if(but->rnaprop && RNA_property_type(but->rnaprop) == PROP_FLOAT) + return 1; + + return 0; +} + +double ui_get_but_val(uiBut *but) +{ + PropertyRNA *prop; + double value = 0.0; + + if(but->editval) { return *(but->editval); } + if(but->poin==NULL && but->rnapoin.data==NULL) return 0.0; + + if(but->rnaprop) { + prop= but->rnaprop; + + switch(RNA_property_type(prop)) { + case PROP_BOOLEAN: + if(RNA_property_array_length(prop)) + value= RNA_property_boolean_get_index(&but->rnapoin, prop, but->rnaindex); + else + value= RNA_property_boolean_get(&but->rnapoin, prop); + break; + case PROP_INT: + if(RNA_property_array_length(prop)) + value= RNA_property_int_get_index(&but->rnapoin, prop, but->rnaindex); + else + value= RNA_property_int_get(&but->rnapoin, prop); + break; + case PROP_FLOAT: + if(RNA_property_array_length(prop)) + value= RNA_property_float_get_index(&but->rnapoin, prop, but->rnaindex); + else + value= RNA_property_float_get(&but->rnapoin, prop); + break; + case PROP_ENUM: + value= RNA_property_enum_get(&but->rnapoin, prop); + break; + default: + value= 0.0; + break; + } + } + else if(but->type== HSVSLI) { + float h, s, v, *fp; + + fp= (but->editvec)? but->editvec: (float *)but->poin; + rgb_to_hsv(fp[0], fp[1], fp[2], &h, &s, &v); + + switch(but->str[0]) { + case 'H': value= h; break; + case 'S': value= s; break; + case 'V': value= v; break; + } + } + else if( but->pointype == CHA ) { + value= *(char *)but->poin; + } + else if( but->pointype == SHO ) { + value= *(short *)but->poin; + } + else if( but->pointype == INT ) { + value= *(int *)but->poin; + } + else if( but->pointype == FLO ) { + value= *(float *)but->poin; + } + + return value; +} + +void ui_set_but_val(uiBut *but, double value) +{ + PropertyRNA *prop; + + /* value is a hsv value: convert to rgb */ + if(but->rnaprop) { + prop= but->rnaprop; + + if(RNA_property_editable(&but->rnapoin, prop)) { + switch(RNA_property_type(prop)) { + case PROP_BOOLEAN: + if(RNA_property_array_length(prop)) + RNA_property_boolean_set_index(&but->rnapoin, prop, but->rnaindex, value); + else + RNA_property_boolean_set(&but->rnapoin, prop, value); + break; + case PROP_INT: + if(RNA_property_array_length(prop)) + RNA_property_int_set_index(&but->rnapoin, prop, but->rnaindex, value); + else + RNA_property_int_set(&but->rnapoin, prop, value); + break; + case PROP_FLOAT: + if(RNA_property_array_length(prop)) + RNA_property_float_set_index(&but->rnapoin, prop, but->rnaindex, value); + else + RNA_property_float_set(&but->rnapoin, prop, value); + break; + case PROP_ENUM: + RNA_property_enum_set(&but->rnapoin, prop, value); + break; + default: + break; + } + } + } + else if(but->pointype==0); + else if(but->type==HSVSLI ) { + float h, s, v, *fp; + + fp= (but->editvec)? but->editvec: (float *)but->poin; + rgb_to_hsv(fp[0], fp[1], fp[2], &h, &s, &v); + + switch(but->str[0]) { + case 'H': h= value; break; + case 'S': s= value; break; + case 'V': v= value; break; + } + + hsv_to_rgb(h, s, v, fp, fp+1, fp+2); + + } + else { + /* first do rounding */ + if(but->pointype==CHA) + value= (char)floor(value+0.5); + else if(but->pointype==SHO ) { + /* gcc 3.2.1 seems to have problems + * casting a double like 32772.0 to + * a short so we cast to an int, then + to a short */ + int gcckludge; + gcckludge = (int) floor(value+0.5); + value= (short)gcckludge; + } + else if(but->pointype==INT ) + value= (int)floor(value+0.5); + else if(but->pointype==FLO ) { + float fval= (float)value; + if(fval>= -0.00001f && fval<= 0.00001f) fval= 0.0f; /* prevent negative zero */ + value= fval; + } + + /* then set value with possible edit override */ + if(but->editval) + *but->editval= value; + else if(but->pointype==CHA) + *((char *)but->poin)= (char)value; + else if(but->pointype==SHO) + *((short *)but->poin)= (short)value; + else if(but->pointype==INT) + *((int *)but->poin)= (int)value; + else if(but->pointype==FLO) + *((float *)but->poin)= (float)value; + } + + /* update select flag */ + ui_is_but_sel(but); +} + +int ui_get_but_string_max_length(uiBut *but) +{ + if(but->type == TEX) + return but->hardmax; + else if(but->type == IDPOIN) + return sizeof(((ID*)NULL)->name)-2; + else + return UI_MAX_DRAW_STR; +} + +void ui_get_but_string(uiBut *but, char *str, int maxlen) +{ + if(but->rnaprop && ELEM3(but->type, TEX, IDPOIN, SEARCH_MENU)) { + PropertyType type; + char *buf= NULL; + + type= RNA_property_type(but->rnaprop); + + if(type == PROP_STRING) { + /* RNA string */ + buf= RNA_property_string_get_alloc(&but->rnapoin, but->rnaprop, str, maxlen); + } + else if(type == PROP_POINTER) { + /* RNA pointer */ + PointerRNA ptr= RNA_property_pointer_get(&but->rnapoin, but->rnaprop); + PropertyRNA *nameprop; + + if(ptr.data && (nameprop = RNA_struct_name_property(ptr.type))) + buf= RNA_property_string_get_alloc(&ptr, nameprop, str, maxlen); + else + BLI_strncpy(str, "", maxlen); + } + else + BLI_strncpy(str, "", maxlen); + + if(buf && buf != str) { + /* string was too long, we have to truncate */ + BLI_strncpy(str, buf, maxlen); + MEM_freeN(buf); + } + } + else if(but->type == IDPOIN) { + /* ID pointer */ + ID *id= *(but->idpoin_idpp); + + if(id) BLI_strncpy(str, id->name+2, maxlen); + else BLI_strncpy(str, "", maxlen); + + return; + } + else if(but->type == TEX) { + /* string */ + BLI_strncpy(str, but->poin, maxlen); + return; + } + else if(but->type == SEARCH_MENU) { + /* string */ + BLI_strncpy(str, but->poin, maxlen); + return; + } + else { + /* number */ + double value; + + value= ui_get_but_val(but); + + if(ui_is_but_float(but)) { + if(but->a2) { /* amount of digits defined */ + if(but->a2==1) BLI_snprintf(str, maxlen, "%.1f", value); + else if(but->a2==2) BLI_snprintf(str, maxlen, "%.2f", value); + else if(but->a2==3) BLI_snprintf(str, maxlen, "%.3f", value); + else BLI_snprintf(str, maxlen, "%.4f", value); + } + else + BLI_snprintf(str, maxlen, "%.3f", value); + } + else + BLI_snprintf(str, maxlen, "%d", (int)value); + } +} + +static void ui_rna_ID_collection(bContext *C, uiBut *but, PointerRNA *ptr, PropertyRNA **prop) +{ + CollectionPropertyIterator iter; + PropertyRNA *iterprop, *iprop; + StructRNA *srna; + + /* look for collection property in Main */ + RNA_pointer_create(NULL, &RNA_Main, CTX_data_main(C), ptr); + + iterprop= RNA_struct_iterator_property(ptr->type); + RNA_property_collection_begin(ptr, iterprop, &iter); + *prop= NULL; + + for(; iter.valid; RNA_property_collection_next(&iter)) { + iprop= iter.ptr.data; + + /* if it's a collection and has same pointer type, we've got it */ + if(RNA_property_type(iprop) == PROP_COLLECTION) { + srna= RNA_property_pointer_type(ptr, iprop); + + if(RNA_property_pointer_type(ptr, but->rnaprop) == srna) { + *prop= iprop; + break; + } + } + } + + RNA_property_collection_end(&iter); +} + +/* autocomplete callback for RNA pointers */ +static void ui_rna_ID_autocomplete(bContext *C, char *str, void *arg_but) +{ + uiBut *but= arg_but; + AutoComplete *autocpl; + CollectionPropertyIterator iter; + PointerRNA ptr; + PropertyRNA *prop, *nameprop; + char *name; + + if(str[0]==0) return; + + /* get the collection */ + ui_rna_ID_collection(C, but, &ptr, &prop); + if(prop==NULL) return; + + autocpl= autocomplete_begin(str, ui_get_but_string_max_length(but)); + RNA_property_collection_begin(&ptr, prop, &iter); + + /* loop over items in collection */ + for(; iter.valid; RNA_property_collection_next(&iter)) { + if(iter.ptr.data && (nameprop = RNA_struct_name_property(iter.ptr.type))) { + name= RNA_property_string_get_alloc(&iter.ptr, nameprop, NULL, 0); + + if(name) { + /* test item name */ + autocomplete_do_name(autocpl, name); + MEM_freeN(name); + } + } + } + + RNA_property_collection_end(&iter); + autocomplete_end(autocpl, str); +} + +int ui_set_but_string(bContext *C, uiBut *but, const char *str) +{ + if(but->rnaprop && ELEM3(but->type, TEX, IDPOIN, SEARCH_MENU)) { + if(RNA_property_editable(&but->rnapoin, but->rnaprop)) { + PropertyType type; + + type= RNA_property_type(but->rnaprop); + + if(type == PROP_STRING) { + /* RNA string */ + RNA_property_string_set(&but->rnapoin, but->rnaprop, str); + return 1; + } + else if(type == PROP_POINTER) { + /* RNA pointer */ + PointerRNA ptr, rptr; + PropertyRNA *prop; + + /* XXX only ID pointers at the moment, needs to support + * custom collection too for bones, vertex groups, .. */ + ui_rna_ID_collection(C, but, &ptr, &prop); + + if(str == NULL || str[0] == '\0') { + memset(&rptr, 0, sizeof(rptr)); + RNA_property_pointer_set(&but->rnapoin, but->rnaprop, rptr); + return 1; + } + else if(prop && RNA_property_collection_lookup_string(&ptr, prop, str, &rptr)) { + RNA_property_pointer_set(&but->rnapoin, but->rnaprop, rptr); + return 1; + } + else + return 0; + } + } + } + else if(but->type == IDPOIN) { + /* ID pointer */ + but->idpoin_func(C, (char*)str, but->idpoin_idpp); + return 1; + } + else if(but->type == TEX) { + /* string */ + BLI_strncpy(but->poin, str, but->hardmax); + return 1; + } + else if(but->type == SEARCH_MENU) { + /* string */ + BLI_strncpy(but->poin, str, but->hardmax); + return 1; + } + else { + double value; + + /* XXX 2.50 missing python api */ +#if 0 + if(BPY_button_eval(str, &value)) { + BKE_report(CTX_reports(C), RPT_WARNING, "Invalid Python expression, check console"); + value = 0.0f; /* Zero out value on error */ + + if(str[0]) + return 0; + } +#else + value= atof(str); +#endif + + if(!ui_is_but_float(but)) value= (int)value; + if(but->type==NUMABS) value= fabs(value); + + /* not that we use hard limits here */ + if(value<but->hardmin) value= but->hardmin; + if(value>but->hardmax) value= but->hardmax; + + ui_set_but_val(but, value); + return 1; + } + + return 0; +} + +static double soft_range_round_up(double value, double max) +{ + /* round up to .., 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, .. */ + double newmax= pow(10.0, ceil(log(value)/log(10.0))); + + if(newmax*0.2 >= max && newmax*0.2 >= value) + return newmax*0.2; + else if(newmax*0.5 >= max && newmax*0.5 >= value) + return newmax*0.5; + else + return newmax; +} + +static double soft_range_round_down(double value, double max) +{ + /* round down to .., 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, .. */ + double newmax= pow(10.0, floor(log(value)/log(10.0))); + + if(newmax*5.0 <= max && newmax*5.0 <= value) + return newmax*5.0; + else if(newmax*2.0 <= max && newmax*2.0 <= value) + return newmax*2.0; + else + return newmax; +} + +void ui_set_but_soft_range(uiBut *but, double value) +{ + PropertyType type; + double softmin, softmax, step, precision; + + if(but->rnaprop) { + type= RNA_property_type(but->rnaprop); + + if(type == PROP_INT) { + int imin, imax, istep; + + RNA_property_int_ui_range(&but->rnapoin, but->rnaprop, &imin, &imax, &istep); + softmin= imin; + softmax= imax; + step= istep; + precision= 1; + } + else if(type == PROP_FLOAT) { + float fmin, fmax, fstep, fprecision; + + RNA_property_float_ui_range(&but->rnapoin, but->rnaprop, &fmin, &fmax, &fstep, &fprecision); + softmin= fmin; + softmax= fmax; + step= fstep; + precision= fprecision; + } + else + return; + + /* clamp button range to something reasonable in case + * we get -inf/inf from RNA properties */ + softmin= MAX2(softmin, -1e4); + softmax= MIN2(softmax, 1e4); + + /* if the value goes out of the soft/max range, adapt the range */ + if(value+1e-10 < softmin) { + if(value < 0.0) + softmin= -soft_range_round_up(-value, -softmin); + else + softmin= soft_range_round_down(value, softmin); + + if(softmin < but->hardmin) + softmin= but->hardmin; + } + else if(value-1e-10 > softmax) { + if(value < 0.0) + softmax= -soft_range_round_down(-value, -softmax); + else + softmax= soft_range_round_up(value, softmax); + + if(softmax > but->hardmax) + softmax= but->hardmax; + } + + but->softmin= softmin; + but->softmax= softmax; + } +} + +/* ******************* Free ********************/ + +static void ui_free_link(uiLink *link) +{ + if(link) { + BLI_freelistN(&link->lines); + MEM_freeN(link); + } +} + +/* can be called with C==NULL */ +static void ui_free_but(const bContext *C, uiBut *but) +{ + if(but->opptr) { + WM_operator_properties_free(but->opptr); + MEM_freeN(but->opptr); + } + if(but->func_argN) MEM_freeN(but->func_argN); + if(but->active) { + /* XXX solve later, buttons should be free-able without context? */ + if(C) + ui_button_active_cancel(C, but); + else + if(but->active) + MEM_freeN(but->active); + } + if(but->str && but->str != but->strdata) MEM_freeN(but->str); + ui_free_link(but->link); + + MEM_freeN(but); +} + +/* can be called with C==NULL */ +void uiFreeBlock(const bContext *C, uiBlock *block) +{ + uiBut *but; + + while( (but= block->buttons.first) ) { + BLI_remlink(&block->buttons, but); + ui_free_but(C, but); + } + + CTX_store_free_list(&block->contexts); + + BLI_freelistN(&block->saferct); + + MEM_freeN(block); +} + +/* can be called with C==NULL */ +void uiFreeBlocks(const bContext *C, ListBase *lb) +{ + uiBlock *block; + + while( (block= lb->first) ) { + BLI_remlink(lb, block); + uiFreeBlock(C, block); + } +} + +void uiFreeInactiveBlocks(const bContext *C, ListBase *lb) +{ + uiBlock *block, *nextblock; + + for(block=lb->first; block; block=nextblock) { + nextblock= block->next; + + if(!block->handle) { + if(!block->active) { + BLI_remlink(lb, block); + uiFreeBlock(C, block); + } + else + block->active= 0; + } + } +} + +void uiBlockSetRegion(uiBlock *block, ARegion *region) +{ + ListBase *lb; + uiBlock *oldblock= NULL; + + lb= ®ion->uiblocks; + + /* each listbase only has one block with this name, free block + * if is already there so it can be rebuilt from scratch */ + if(lb) { + for (oldblock= lb->first; oldblock; oldblock= oldblock->next) + if (BLI_streq(oldblock->name, block->name)) + break; + + if (oldblock) { + oldblock->active= 0; + oldblock->panel= NULL; + } + } + + block->oldblock= oldblock; + + /* at the beginning of the list! for dynamical menus/blocks */ + if(lb) + BLI_addhead(lb, block); +} + +uiBlock *uiBeginBlock(const bContext *C, ARegion *region, const char *name, short dt) +{ + uiBlock *block; + wmWindow *window; + int getsizex, getsizey; + + window= CTX_wm_window(C); + + block= MEM_callocN(sizeof(uiBlock), "uiBlock"); + block->active= 1; + block->dt= dt; + BLI_strncpy(block->name, name, sizeof(block->name)); + + if(region) + uiBlockSetRegion(block, region); + + /* window matrix and aspect */ + if(region && region->swinid) { + wm_subwindow_getmatrix(window, region->swinid, block->winmat); + wm_subwindow_getsize(window, region->swinid, &getsizex, &getsizey); + + /* TODO - investigate why block->winmat[0][0] is negative + * in the image view when viewRedrawForce is called */ + block->aspect= 2.0/fabs( (getsizex)*block->winmat[0][0]); + } + else { + /* no subwindow created yet, for menus for example, so we + * use the main window instead, since buttons are created + * there anyway */ + wm_subwindow_getmatrix(window, window->screen->mainwin, block->winmat); + wm_subwindow_getsize(window, window->screen->mainwin, &getsizex, &getsizey); + + block->aspect= 2.0/fabs(getsizex*block->winmat[0][0]); + block->auto_open= 2; + block->flag |= UI_BLOCK_LOOP; /* tag as menu */ + } + + return block; +} + +uiBlock *uiGetBlock(char *name, ARegion *ar) +{ + uiBlock *block= ar->uiblocks.first; + + while(block) { + if( strcmp(name, block->name)==0 ) return block; + block= block->next; + } + + return NULL; +} + +void uiBlockSetEmboss(uiBlock *block, short dt) +{ + block->dt= dt; +} + +void ui_check_but(uiBut *but) +{ + /* if something changed in the button */ + double value; + float okwidth; +// int transopts= ui_translate_buttons(); + + ui_is_but_sel(but); + +// if(but->type==TEX || but->type==IDPOIN) transopts= 0; + + /* test for min and max, icon sliders, etc */ + switch( but->type ) { + case NUM: + case SLI: + case SCROLL: + case NUMSLI: + case HSVSLI: + value= ui_get_but_val(but); + if(value < but->hardmin) ui_set_but_val(but, but->hardmin); + else if(value > but->hardmax) ui_set_but_val(but, but->hardmax); + break; + + case NUMABS: + value= fabs( ui_get_but_val(but) ); + if(value < but->hardmin) ui_set_but_val(but, but->hardmin); + else if(value > but->hardmax) ui_set_but_val(but, but->hardmax); + break; + + case ICONTOG: + case ICONTOGN: + if(but->flag & UI_SELECT) but->iconadd= 1; + else but->iconadd= 0; + break; + + case ICONROW: + value= ui_get_but_val(but); + but->iconadd= (int)value- (int)(but->hardmin); + break; + + case ICONTEXTROW: + value= ui_get_but_val(but); + but->iconadd= (int)value- (int)(but->hardmin); + break; + } + + + /* safety is 4 to enable small number buttons (like 'users') */ + okwidth= -4 + (but->x2 - but->x1); + + /* name: */ + switch( but->type ) { + + case MENU: + case ICONTEXTROW: + + if(but->x2 - but->x1 > 24) { + value= ui_get_but_val(but); + ui_set_name_menu(but, (int)value); + } + break; + + case NUM: + case NUMSLI: + case HSVSLI: + case NUMABS: + + value= ui_get_but_val(but); + + if(ui_is_but_float(but)) { + if(value == FLT_MAX) sprintf(but->drawstr, "%sinf", but->str); + else if(value == -FLT_MAX) sprintf(but->drawstr, "%s-inf", but->str); + else if(but->a2) { /* amount of digits defined */ + if(but->a2==1) sprintf(but->drawstr, "%s%.1f", but->str, value); + else if(but->a2==2) sprintf(but->drawstr, "%s%.2f", but->str, value); + else if(but->a2==3) sprintf(but->drawstr, "%s%.3f", but->str, value); + else sprintf(but->drawstr, "%s%.4f", but->str, value); + } + else { + if(but->hardmax<10.001) sprintf(but->drawstr, "%s%.3f", but->str, value); + else sprintf(but->drawstr, "%s%.2f", but->str, value); + } + } + else { + sprintf(but->drawstr, "%s%d", but->str, (int)value); + } + + if(but->rnaprop) { + PropertySubType pstype = RNA_property_subtype(but->rnaprop); + + if (pstype == PROP_PERCENTAGE) + strcat(but->drawstr, "%"); + } + break; + + case LABEL: + if(ui_is_but_float(but)) { + value= ui_get_but_val(but); + if(but->a2) { /* amount of digits defined */ + if(but->a2==1) sprintf(but->drawstr, "%s%.1f", but->str, value); + else if(but->a2==2) sprintf(but->drawstr, "%s%.2f", but->str, value); + else if(but->a2==3) sprintf(but->drawstr, "%s%.3f", but->str, value); + else sprintf(but->drawstr, "%s%.4f", but->str, value); + } + else { + sprintf(but->drawstr, "%s%.2f", but->str, value); + } + } + else strcpy(but->drawstr, but->str); + + break; + + case IDPOIN: + case TEX: + case SEARCH_MENU: + if(!but->editstr) { + char str[UI_MAX_DRAW_STR]; + + ui_get_but_string(but, str, UI_MAX_DRAW_STR-strlen(but->str)); + + strcpy(but->drawstr, but->str); + strcat(but->drawstr, str); + } + break; + + case KEYEVT: + strcpy(but->drawstr, but->str); + if (but->flag & UI_SELECT) { + strcat(but->drawstr, "Press a key"); + } else { + strcat(but->drawstr, WM_key_event_string((short) ui_get_but_val(but))); + } + break; + + case BUT_TOGDUAL: + /* trying to get the dual-icon to left of text... not very nice */ + if(but->str[0]) { + strcpy(but->drawstr, " "); + strcpy(but->drawstr+2, but->str); + } + break; + default: + strcpy(but->drawstr, but->str); + + } + + /* if we are doing text editing, this will override the drawstr */ + if(but->editstr) { + strcpy(but->drawstr, but->str); + strcat(but->drawstr, but->editstr); + } + + /* text clipping moved to widget drawing code itself */ +} + + +void uiBlockBeginAlign(uiBlock *block) +{ + /* if other align was active, end it */ + if(block->flag & UI_BUT_ALIGN) uiBlockEndAlign(block); + + block->flag |= UI_BUT_ALIGN_DOWN; + block->alignnr++; + + /* buttons declared after this call will get this align nr */ // XXX flag? +} + +static int buts_are_horiz(uiBut *but1, uiBut *but2) +{ + float dx, dy; + + dx= fabs( but1->x2 - but2->x1); + dy= fabs( but1->y1 - but2->y2); + + if(dx > dy) return 0; + return 1; +} + +void uiBlockEndAlign(uiBlock *block) +{ + block->flag &= ~UI_BUT_ALIGN; // all 4 flags +} + +int ui_but_can_align(uiBut *but) +{ + return !ELEM3(but->type, LABEL, OPTION, OPTIONN); +} + +static void ui_block_do_align_but(uiBlock *block, uiBut *first, int nr) +{ + uiBut *prev, *but=NULL, *next; + int flag= 0, cols=0, rows=0; + + /* auto align */ + + for(but=first; but && but->alignnr == nr; but=but->next) { + if(but->next && but->next->alignnr == nr) { + if(buts_are_horiz(but, but->next)) cols++; + else rows++; + } + } + + /* rows==0: 1 row, cols==0: 1 collumn */ + + /* note; how it uses 'flag' in loop below (either set it, or OR it) is confusing */ + for(but=first, prev=NULL; but && but->alignnr == nr; prev=but, but=but->next) { + next= but->next; + if(next && next->alignnr != nr) + next= NULL; + + /* clear old flag */ + but->flag &= ~UI_BUT_ALIGN; + + if(flag==0) { /* first case */ + if(next) { + if(buts_are_horiz(but, next)) { + if(rows==0) + flag= UI_BUT_ALIGN_RIGHT; + else + flag= UI_BUT_ALIGN_DOWN|UI_BUT_ALIGN_RIGHT; + } + else { + flag= UI_BUT_ALIGN_DOWN; + } + } + } + else if(next==NULL) { /* last case */ + if(prev) { + if(buts_are_horiz(prev, but)) { + if(rows==0) + flag= UI_BUT_ALIGN_LEFT; + else + flag= UI_BUT_ALIGN_TOP|UI_BUT_ALIGN_LEFT; + } + else flag= UI_BUT_ALIGN_TOP; + } + } + else if(buts_are_horiz(but, next)) { + /* check if this is already second row */ + if( prev && buts_are_horiz(prev, but)==0) { + flag &= ~UI_BUT_ALIGN_LEFT; + flag |= UI_BUT_ALIGN_TOP; + /* exception case: bottom row */ + if(rows>0) { + uiBut *bt= but; + while(bt && bt->alignnr == nr) { + if(bt->next && bt->next->alignnr == nr && buts_are_horiz(bt, bt->next)==0 ) break; + bt= bt->next; + } + if(bt==0 || bt->alignnr != nr) flag= UI_BUT_ALIGN_TOP|UI_BUT_ALIGN_RIGHT; + } + } + else flag |= UI_BUT_ALIGN_LEFT; + } + else { + if(cols==0) { + flag |= UI_BUT_ALIGN_TOP; + } + else { /* next button switches to new row */ + + if(prev && buts_are_horiz(prev, but)) + flag |= UI_BUT_ALIGN_LEFT; + + if( (flag & UI_BUT_ALIGN_TOP)==0) { /* stil top row */ + if(prev) + flag= UI_BUT_ALIGN_DOWN|UI_BUT_ALIGN_LEFT; + else + flag |= UI_BUT_ALIGN_DOWN; + } + else + flag |= UI_BUT_ALIGN_TOP; + } + } + + but->flag |= flag; + + /* merge coordinates */ + if(prev) { + // simple cases + if(rows==0) { + but->x1= (prev->x2+but->x1)/2.0; + prev->x2= but->x1; + } + else if(cols==0) { + but->y2= (prev->y1+but->y2)/2.0; + prev->y1= but->y2; + } + else { + if(buts_are_horiz(prev, but)) { + but->x1= (prev->x2+but->x1)/2.0; + prev->x2= but->x1; + /* copy height too */ + but->y2= prev->y2; + } + else if(prev->prev && buts_are_horiz(prev->prev, prev)==0) { + /* the previous button is a single one in its row */ + but->y2= (prev->y1+but->y2)/2.0; + prev->y1= but->y2; + } + else { + /* the previous button is not a single one in its row */ + but->y2= prev->y1; + } + } + } + } +} + +void ui_block_do_align(uiBlock *block) +{ + uiBut *but; + int nr; + + /* align buttons with same align nr */ + for(but=block->buttons.first; but;) { + if(but->alignnr) { + nr= but->alignnr; + ui_block_do_align_but(block, but, nr); + + /* skip with same number */ + for(; but && but->alignnr == nr; but=but->next); + + if(!but) + break; + } + else + but= but->next; + } +} + +/* +ui_def_but is the function that draws many button types + +for float buttons: + "a1" Click Step (how much to change the value each click) + "a2" Number of decimal point values to display. 0 defaults to 3 (0.000) 1,2,3, and a maximum of 4, + all greater values will be clamped to 4. + +*/ +static uiBut *ui_def_but(uiBlock *block, int type, int retval, char *str, short x1, short y1, short x2, short y2, void *poin, float min, float max, float a1, float a2, char *tip) +{ + uiBut *but; + short slen; + + if(type & BUTPOIN) { /* a pointer is required */ + if(poin==NULL) + return NULL; + } + + but= MEM_callocN(sizeof(uiBut), "uiBut"); + + but->type= type & BUTTYPE; + but->pointype= type & BUTPOIN; + but->bit= type & BIT; + but->bitnr= type & 31; + but->icon = 0; + + but->retval= retval; + if( strlen(str)>=UI_MAX_NAME_STR-1 ) { + but->str= MEM_callocN( strlen(str)+2, "uiDefBut"); + strcpy(but->str, str); + } + else { + but->str= but->strdata; + strcpy(but->str, str); + } + but->x1= x1; + but->y1= y1; + but->x2= (x1+x2); + but->y2= (y1+y2); + + but->poin= poin; + but->hardmin= but->softmin= min; + but->hardmax= but->softmax= max; + but->a1= a1; + but->a2= a2; + but->tip= tip; + + but->lock= block->lock; + but->lockstr= block->lockstr; + but->dt= block->dt; + + but->aspect= 1.0f; //XXX block->aspect; + but->block= block; // pointer back, used for frontbuffer status, and picker + + if((block->flag & UI_BUT_ALIGN) && ui_but_can_align(but)) + but->alignnr= block->alignnr; + + but->func= block->func; + but->func_arg1= block->func_arg1; + but->func_arg2= block->func_arg2; + + but->pos= -1; /* cursor invisible */ + + if(ELEM(but->type, NUM, NUMABS)) { /* add a space to name */ + slen= strlen(but->str); + if(slen>0 && slen<UI_MAX_NAME_STR-2) { + if(but->str[slen-1]!=' ') { + but->str[slen]= ' '; + but->str[slen+1]= 0; + } + } + } + + if(but->type==HSVCUBE) { /* hsv buttons temp storage */ + float rgb[3]; + ui_get_but_vectorf(but, rgb); + rgb_to_hsv(rgb[0], rgb[1], rgb[2], but->hsv, but->hsv+1, but->hsv+2); + } + + if((block->flag & UI_BLOCK_LOOP) || ELEM6(but->type, MENU, TEX, LABEL, IDPOIN, BLOCK, BUTM)) { + but->flag |= UI_TEXT_LEFT; + } + + if(but->type==BUT_TOGDUAL) { + but->flag |= UI_ICON_LEFT; + } + + but->flag |= (block->flag & UI_BUT_ALIGN); + + if (but->lock) { + if (but->lockstr) { + but->flag |= UI_BUT_DISABLED; + } + } + + BLI_addtail(&block->buttons, but); + + if(block->curlayout) + ui_layout_add_but(block->curlayout, but); + + return but; +} + +uiBut *ui_def_but_rna(uiBlock *block, int type, int retval, char *str, short x1, short y1, short x2, short y2, PointerRNA *ptr, const char *propname, int index, float min, float max, float a1, float a2, char *tip) +{ + uiBut *but; + PropertyRNA *prop; + PropertyType proptype; + int freestr= 0; + + prop= RNA_struct_find_property(ptr, propname); + + if(prop) { + proptype= RNA_property_type(prop); + + /* use rna values if parameters are not specified */ + if(!str) { + if(type == MENU && proptype == PROP_ENUM) { + const EnumPropertyItem *item; + DynStr *dynstr; + int i, totitem; + + RNA_property_enum_items(ptr, prop, &item, &totitem); + + dynstr= BLI_dynstr_new(); + BLI_dynstr_appendf(dynstr, "%s%%t", RNA_property_ui_name(prop)); + for(i=0; i<totitem; i++) + BLI_dynstr_appendf(dynstr, "|%s %%x%d", item[i].name, item[i].value); + str= BLI_dynstr_get_cstring(dynstr); + BLI_dynstr_free(dynstr); + + freestr= 1; + } + else if(type == ROW && proptype == PROP_ENUM) { + const EnumPropertyItem *item; + int i, totitem; + + RNA_property_enum_items(ptr, prop, &item, &totitem); + for(i=0; i<totitem; i++) + if(item[i].value == (int)max) + str= (char*)item[i].name; + + if(!str) + str= (char*)RNA_property_ui_name(prop); + } + else + str= (char*)RNA_property_ui_name(prop); + } + + if(!tip) { + if(type == ROW && proptype == PROP_ENUM) { + const EnumPropertyItem *item; + int i, totitem; + + RNA_property_enum_items(ptr, prop, &item, &totitem); + + for(i=0; i<totitem; i++) { + if(item[i].value == (int)max) { + if(item[i].description[0]) + tip= (char*)item[i].description; + break; + } + } + } + } + + if(!tip) + tip= (char*)RNA_property_ui_description(prop); + + if(min == max || a1 == -1 || a2 == -1) { + if(proptype == PROP_INT) { + int hardmin, hardmax, softmin, softmax, step; + + RNA_property_int_range(ptr, prop, &hardmin, &hardmax); + RNA_property_int_ui_range(ptr, prop, &softmin, &softmax, &step); + + if(min == max) { + min= hardmin; + max= hardmax; + } + if(a1 == -1) + a1= step; + if(a2 == -1) + a2= 0; + } + else if(proptype == PROP_FLOAT) { + float hardmin, hardmax, softmin, softmax, step, precision; + + RNA_property_float_range(ptr, prop, &hardmin, &hardmax); + RNA_property_float_ui_range(ptr, prop, &softmin, &softmax, &step, &precision); + + if(min == max) { + min= hardmin; + max= hardmax; + } + if(a1 == -1) + a1= step; + if(a2 == -1) + a2= precision; + } + else if(proptype == PROP_STRING) { + min= 0; + max= RNA_property_string_maxlength(prop); + if(max == 0) /* interface code should ideally support unlimited length */ + max= UI_MAX_DRAW_STR; + } + } + } + else + str= (char*)propname; + + /* now create button */ + but= ui_def_but(block, type, retval, str, x1, y1, x2, y2, NULL, min, max, a1, a2, tip); + + if(prop) { + but->rnapoin= *ptr; + but->rnaprop= prop; + + if(RNA_property_array_length(but->rnaprop)) + but->rnaindex= index; + else + but->rnaindex= 0; + + if(type == IDPOIN) + uiButSetCompleteFunc(but, ui_rna_ID_autocomplete, but); + } + + if (!prop || !RNA_property_editable(&but->rnapoin, prop)) { + but->flag |= UI_BUT_DISABLED; + but->lock = 1; + but->lockstr = ""; + } + + if(freestr) + MEM_freeN(str); + + return but; +} + +uiBut *ui_def_but_operator(uiBlock *block, int type, char *opname, int opcontext, char *str, short x1, short y1, short x2, short y2, char *tip) +{ + uiBut *but; + wmOperatorType *ot; + + ot= WM_operatortype_find(opname); + + if(!str) { + if(ot) str= ot->name; + else str= opname; + } + + if ((!tip || tip[0]=='\0') && ot && ot->description) { + tip= ot->description; + } + + but= ui_def_but(block, type, -1, str, x1, y1, x2, y2, NULL, 0, 0, 0, 0, tip); + but->optype= ot; + but->opcontext= opcontext; + + if(!ot) { + but->flag |= UI_BUT_DISABLED; + but->lock = 1; + but->lockstr = ""; + } + + return but; +} + +uiBut *uiDefBut(uiBlock *block, int type, int retval, char *str, short x1, short y1, short x2, short y2, void *poin, float min, float max, float a1, float a2, char *tip) +{ + uiBut *but= ui_def_but(block, type, retval, str, x1, y1, x2, y2, poin, min, max, a1, a2, tip); + + ui_check_but(but); + + return but; +} + + /* if _x_ is a power of two (only one bit) return the power, + * otherwise return -1. + * (1<<findBitIndex(x))==x for powers of two. + */ +static int findBitIndex(unsigned int x) { + if (!x || (x&(x-1))!=0) { /* x&(x-1) strips lowest bit */ + return -1; + } else { + int idx= 0; + + if (x&0xFFFF0000) idx+=16, x>>=16; + if (x&0xFF00) idx+=8, x>>=8; + if (x&0xF0) idx+=4, x>>=4; + if (x&0xC) idx+=2, x>>=2; + if (x&0x2) idx+=1; + + return idx; + } +} + +/* autocomplete helper functions */ +struct AutoComplete { + int maxlen; + char *truncate; + char *startname; +}; + +AutoComplete *autocomplete_begin(char *startname, int maxlen) +{ + AutoComplete *autocpl; + + autocpl= MEM_callocN(sizeof(AutoComplete), "AutoComplete"); + autocpl->maxlen= maxlen; + autocpl->truncate= MEM_callocN(sizeof(char)*maxlen, "AutoCompleteTruncate"); + autocpl->startname= startname; + + return autocpl; +} + +void autocomplete_do_name(AutoComplete *autocpl, const char *name) +{ + char *truncate= autocpl->truncate; + char *startname= autocpl->startname; + int a; + + for(a=0; a<autocpl->maxlen-1; a++) { + if(startname[a]==0 || startname[a]!=name[a]) + break; + } + /* found a match */ + if(startname[a]==0) { + /* first match */ + if(truncate[0]==0) + BLI_strncpy(truncate, name, autocpl->maxlen); + else { + /* remove from truncate what is not in bone->name */ + for(a=0; a<autocpl->maxlen-1; a++) { + if(truncate[a]!=name[a]) + truncate[a]= 0; + } + } + } +} + +void autocomplete_end(AutoComplete *autocpl, char *autoname) +{ + if(autocpl->truncate[0]) + BLI_strncpy(autoname, autocpl->truncate, autocpl->maxlen); + else { + if (autoname != autocpl->startname) /* dont copy a string over its self */ + BLI_strncpy(autoname, autocpl->startname, autocpl->maxlen); + } + MEM_freeN(autocpl->truncate); + MEM_freeN(autocpl); +} + +/* autocomplete callback for ID buttons */ +static void autocomplete_id(bContext *C, char *str, void *arg_v) +{ + int blocktype= (intptr_t)arg_v; + ListBase *listb= wich_libbase(CTX_data_main(C), blocktype); + + if(listb==NULL) return; + + /* search if str matches the beginning of an ID struct */ + if(str[0]) { + AutoComplete *autocpl= autocomplete_begin(str, 22); + ID *id; + + for(id= listb->first; id; id= id->next) + autocomplete_do_name(autocpl, id->name+2); + + autocomplete_end(autocpl, str); + } +} + +static uiBut *uiDefButBit(uiBlock *block, int type, int bit, int retval, char *str, short x1, short y1, short x2, short y2, void *poin, float min, float max, float a1, float a2, char *tip) +{ + int bitIdx= findBitIndex(bit); + if (bitIdx==-1) { + return NULL; + } else { + return uiDefBut(block, type|BIT|bitIdx, retval, str, x1, y1, x2, y2, poin, min, max, a1, a2, tip); + } +} +uiBut *uiDefButF(uiBlock *block, int type, int retval, char *str, short x1, short y1, short x2, short y2, float *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefBut(block, type|FLO, retval, str, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefButBitF(uiBlock *block, int type, int bit, int retval, char *str, short x1, short y1, short x2, short y2, float *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefButBit(block, type|FLO, bit, retval, str, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefButI(uiBlock *block, int type, int retval, char *str, short x1, short y1, short x2, short y2, int *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefBut(block, type|INT, retval, str, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefButBitI(uiBlock *block, int type, int bit, int retval, char *str, short x1, short y1, short x2, short y2, int *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefButBit(block, type|INT, bit, retval, str, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefButS(uiBlock *block, int type, int retval, char *str, short x1, short y1, short x2, short y2, short *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefBut(block, type|SHO, retval, str, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefButBitS(uiBlock *block, int type, int bit, int retval, char *str, short x1, short y1, short x2, short y2, short *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefButBit(block, type|SHO, bit, retval, str, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefButC(uiBlock *block, int type, int retval, char *str, short x1, short y1, short x2, short y2, char *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefBut(block, type|CHA, retval, str, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefButBitC(uiBlock *block, int type, int bit, int retval, char *str, short x1, short y1, short x2, short y2, char *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefButBit(block, type|CHA, bit, retval, str, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefButR(uiBlock *block, int type, int retval, char *str, short x1, short y1, short x2, short y2, PointerRNA *ptr, const char *propname, int index, float min, float max, float a1, float a2, char *tip) +{ + uiBut *but; + + but= ui_def_but_rna(block, type, retval, str, x1, y1, x2, y2, ptr, propname, index, min, max, a1, a2, tip); + if(but) + ui_check_but(but); + + return but; +} +uiBut *uiDefButO(uiBlock *block, int type, char *opname, int opcontext, char *str, short x1, short y1, short x2, short y2, char *tip) +{ + uiBut *but; + + but= ui_def_but_operator(block, type, opname, opcontext, str, x1, y1, x2, y2, tip); + if(but) + ui_check_but(but); + + return but; +} + +uiBut *uiDefIconBut(uiBlock *block, int type, int retval, int icon, short x1, short y1, short x2, short y2, void *poin, float min, float max, float a1, float a2, char *tip) +{ + uiBut *but= ui_def_but(block, type, retval, "", x1, y1, x2, y2, poin, min, max, a1, a2, tip); + + but->icon= (BIFIconID) icon; + but->flag|= UI_HAS_ICON; + + ui_check_but(but); + + return but; +} +static uiBut *uiDefIconButBit(uiBlock *block, int type, int bit, int retval, int icon, short x1, short y1, short x2, short y2, void *poin, float min, float max, float a1, float a2, char *tip) +{ + int bitIdx= findBitIndex(bit); + if (bitIdx==-1) { + return NULL; + } else { + return uiDefIconBut(block, type|BIT|bitIdx, retval, icon, x1, y1, x2, y2, poin, min, max, a1, a2, tip); + } +} + +uiBut *uiDefIconButF(uiBlock *block, int type, int retval, int icon, short x1, short y1, short x2, short y2, float *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefIconBut(block, type|FLO, retval, icon, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefIconButBitF(uiBlock *block, int type, int bit, int retval, int icon, short x1, short y1, short x2, short y2, float *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefIconButBit(block, type|FLO, bit, retval, icon, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefIconButI(uiBlock *block, int type, int retval, int icon, short x1, short y1, short x2, short y2, int *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefIconBut(block, type|INT, retval, icon, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefIconButBitI(uiBlock *block, int type, int bit, int retval, int icon, short x1, short y1, short x2, short y2, int *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefIconButBit(block, type|INT, bit, retval, icon, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefIconButS(uiBlock *block, int type, int retval, int icon, short x1, short y1, short x2, short y2, short *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefIconBut(block, type|SHO, retval, icon, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefIconButBitS(uiBlock *block, int type, int bit, int retval, int icon, short x1, short y1, short x2, short y2, short *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefIconButBit(block, type|SHO, bit, retval, icon, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefIconButC(uiBlock *block, int type, int retval, int icon, short x1, short y1, short x2, short y2, char *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefIconBut(block, type|CHA, retval, icon, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefIconButBitC(uiBlock *block, int type, int bit, int retval, int icon, short x1, short y1, short x2, short y2, char *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefIconButBit(block, type|CHA, bit, retval, icon, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefIconButR(uiBlock *block, int type, int retval, int icon, short x1, short y1, short x2, short y2, PointerRNA *ptr, const char *propname, int index, float min, float max, float a1, float a2, char *tip) +{ + uiBut *but; + + but= ui_def_but_rna(block, type, retval, "", x1, y1, x2, y2, ptr, propname, index, min, max, a1, a2, tip); + if(but) { + but->icon= (BIFIconID) icon; + but->flag|= UI_HAS_ICON; + ui_check_but(but); + } + + return but; +} +uiBut *uiDefIconButO(uiBlock *block, int type, char *opname, int opcontext, int icon, short x1, short y1, short x2, short y2, char *tip) +{ + uiBut *but; + + but= ui_def_but_operator(block, type, opname, opcontext, "", x1, y1, x2, y2, tip); + if(but) { + but->icon= (BIFIconID) icon; + but->flag|= UI_HAS_ICON; + ui_check_but(but); + } + + return but; +} + +/* Button containing both string label and icon */ +uiBut *uiDefIconTextBut(uiBlock *block, int type, int retval, int icon, char *str, short x1, short y1, short x2, short y2, void *poin, float min, float max, float a1, float a2, char *tip) +{ + uiBut *but= ui_def_but(block, type, retval, str, x1, y1, x2, y2, poin, min, max, a1, a2, tip); + + but->icon= (BIFIconID) icon; + but->flag|= UI_HAS_ICON; + + but->flag|= UI_ICON_LEFT; + + ui_check_but(but); + + return but; +} +static uiBut *uiDefIconTextButBit(uiBlock *block, int type, int bit, int retval, int icon, char *str, short x1, short y1, short x2, short y2, void *poin, float min, float max, float a1, float a2, char *tip) +{ + int bitIdx= findBitIndex(bit); + if (bitIdx==-1) { + return NULL; + } else { + return uiDefIconTextBut(block, type|BIT|bitIdx, retval, icon, str, x1, y1, x2, y2, poin, min, max, a1, a2, tip); + } +} + +uiBut *uiDefIconTextButF(uiBlock *block, int type, int retval, int icon, char *str, short x1, short y1, short x2, short y2, float *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefIconTextBut(block, type|FLO, retval, icon, str, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefIconTextButBitF(uiBlock *block, int type, int bit, int retval, int icon, char *str, short x1, short y1, short x2, short y2, float *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefIconTextButBit(block, type|FLO, bit, retval, icon, str, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefIconTextButI(uiBlock *block, int type, int retval, int icon, char *str, short x1, short y1, short x2, short y2, int *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefIconTextBut(block, type|INT, retval, icon, str, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefIconTextButBitI(uiBlock *block, int type, int bit, int retval, int icon, char *str, short x1, short y1, short x2, short y2, int *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefIconTextButBit(block, type|INT, bit, retval, icon, str, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefIconTextButS(uiBlock *block, int type, int retval, int icon, char *str, short x1, short y1, short x2, short y2, short *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefIconTextBut(block, type|SHO, retval, icon, str, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefIconTextButBitS(uiBlock *block, int type, int bit, int retval, int icon, char *str, short x1, short y1, short x2, short y2, short *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefIconTextButBit(block, type|SHO, bit, retval, icon, str, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefIconTextButC(uiBlock *block, int type, int retval, int icon, char *str, short x1, short y1, short x2, short y2, char *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefIconTextBut(block, type|CHA, retval, icon, str, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefIconTextButBitC(uiBlock *block, int type, int bit, int retval, int icon, char *str, short x1, short y1, short x2, short y2, char *poin, float min, float max, float a1, float a2, char *tip) +{ + return uiDefIconTextButBit(block, type|CHA, bit, retval, icon, str, x1, y1, x2, y2, (void*) poin, min, max, a1, a2, tip); +} +uiBut *uiDefIconTextButR(uiBlock *block, int type, int retval, int icon, char *str, short x1, short y1, short x2, short y2, PointerRNA *ptr, const char *propname, int index, float min, float max, float a1, float a2, char *tip) +{ + uiBut *but; + + but= ui_def_but_rna(block, type, retval, str, x1, y1, x2, y2, ptr, propname, index, min, max, a1, a2, tip); + if(but) { + but->icon= (BIFIconID) icon; + but->flag|= UI_HAS_ICON; + but->flag|= UI_ICON_LEFT; + ui_check_but(but); + } + + return but; +} +uiBut *uiDefIconTextButO(uiBlock *block, int type, char *opname, int opcontext, int icon, char *str, short x1, short y1, short x2, short y2, char *tip) +{ + uiBut *but; + + but= ui_def_but_operator(block, type, opname, opcontext, str, x1, y1, x2, y2, tip); + if(but) { + but->icon= (BIFIconID) icon; + but->flag|= UI_HAS_ICON; + but->flag|= UI_ICON_LEFT; + ui_check_but(but); + } + + return but; +} + +static int ui_menu_y(uiBlock *block) +{ + uiBut *but= block->buttons.last; + + if(but) return but->y1; + else return 0; +} + +uiBut *uiDefMenuButO(uiBlock *block, char *opname, char *name) +{ + int y= ui_menu_y(block) - MENU_ITEM_HEIGHT; + return uiDefIconTextButO(block, BUT, opname, WM_OP_INVOKE_REGION_WIN, ICON_BLANK1, name, 0, y, MENU_WIDTH, MENU_ITEM_HEIGHT-1, NULL); +} + +uiBut *uiDefMenuSep(uiBlock *block) +{ + int y= ui_menu_y(block) - MENU_SEP_HEIGHT; + return uiDefBut(block, SEPR, 0, "", 0, y, MENU_WIDTH, MENU_SEP_HEIGHT, NULL, 0.0, 0.0, 0, 0, ""); +} + +uiBut *uiDefMenuSub(uiBlock *block, uiBlockCreateFunc func, char *name) +{ + int y= ui_menu_y(block) - MENU_ITEM_HEIGHT; + return uiDefIconTextBlockBut(block, func, NULL, ICON_BLANK1, name, 0, y, MENU_WIDTH, MENU_ITEM_HEIGHT-1, ""); +} + +uiBut *uiDefMenuTogR(uiBlock *block, PointerRNA *ptr, char *propname, char *propvalue, char *name) +{ + uiBut *but; + PropertyRNA *prop; + PropertyType type; + const EnumPropertyItem *item; + int a, value, totitem, icon= ICON_CHECKBOX_DEHLT; + int y= ui_menu_y(block) - MENU_ITEM_HEIGHT; + + prop= RNA_struct_find_property(ptr, propname); + if(prop) { + type= RNA_property_type(prop); + + if(type == PROP_BOOLEAN) { + if(RNA_property_boolean_get(ptr, prop)) + icon= ICON_CHECKBOX_HLT; + + return uiDefIconTextButR(block, TOG, 0, icon, name, 0, y, MENU_WIDTH, MENU_ITEM_HEIGHT-1, ptr, propname, 0, 0, 0, 0, 0, NULL); + } + else if(type == PROP_ENUM) { + RNA_property_enum_items(ptr, prop, &item, &totitem); + + value= 0; + for(a=0; a<totitem; a++) { + if(propvalue && strcmp(propvalue, item[a].identifier) == 0) { + value= item[a].value; + if(!name) + name= (char*)item[a].name; + + if(RNA_property_enum_get(ptr, prop) == value) + icon= ICON_CHECKBOX_HLT; + break; + } + } + + if(a != totitem) + return uiDefIconTextButR(block, ROW, 0, icon, name, 0, y, MENU_WIDTH, MENU_ITEM_HEIGHT-1, ptr, propname, 0, 0, value, 0, 0, NULL); + } + } + + /* not found */ + uiBlockSetButLock(block, 1, ""); + but= uiDefIconTextBut(block, BUT, 0, ICON_BLANK1, propname, 0, y, MENU_WIDTH, MENU_ITEM_HEIGHT, NULL, 0.0, 0.0, 0, 0, ""); + uiBlockClearButLock(block); + + return but; +} + +/* END Button containing both string label and icon */ + +void uiSetButLink(uiBut *but, void **poin, void ***ppoin, short *tot, int from, int to) +{ + uiLink *link; + + link= but->link= MEM_callocN(sizeof(uiLink), "new uilink"); + + link->poin= poin; + link->ppoin= ppoin; + link->totlink= tot; + link->fromcode= from; + link->tocode= to; +} + +/* cruft to make uiBlock and uiBut private */ + +int uiBlocksGetYMin(ListBase *lb) +{ + uiBlock *block; + int min= 0; + + for (block= lb->first; block; block= block->next) + if (block==lb->first || block->miny<min) + min= block->miny; + + return min; +} + +void uiBlockSetDirection(uiBlock *block, int direction) +{ + block->direction= direction; +} + +/* this call escapes if there's alignment flags */ +void uiBlockFlipOrder(uiBlock *block) +{ + ListBase lb; + uiBut *but, *next; + float centy, miny=10000, maxy= -10000; + +// if(U.uiflag & USER_PLAINMENUS) +// return; + + for(but= block->buttons.first; but; but= but->next) { + if(but->flag & UI_BUT_ALIGN) return; + if(but->y1 < miny) miny= but->y1; + if(but->y2 > maxy) maxy= but->y2; + } + /* mirror trick */ + centy= (miny+maxy)/2.0; + for(but= block->buttons.first; but; but= but->next) { + but->y1 = centy-(but->y1-centy); + but->y2 = centy-(but->y2-centy); + SWAP(float, but->y1, but->y2); + } + + /* also flip order in block itself, for example for arrowkey */ + lb.first= lb.last= NULL; + but= block->buttons.first; + while(but) { + next= but->next; + BLI_remlink(&block->buttons, but); + BLI_addtail(&lb, but); + but= next; + } + block->buttons= lb; +} + + +void uiBlockSetFlag(uiBlock *block, int flag) +{ + block->flag|= flag; +} + +void uiBlockClearFlag(uiBlock *block, int flag) +{ + block->flag&= ~flag; +} + +void uiBlockSetXOfs(uiBlock *block, int xofs) +{ + block->xofs= xofs; +} + +void uiButSetFlag(uiBut *but, int flag) +{ + but->flag|= flag; +} + +void uiButClearFlag(uiBut *but, int flag) +{ + but->flag&= ~flag; +} + +int uiButGetRetVal(uiBut *but) +{ + return but->retval; +} + +PointerRNA *uiButGetOperatorPtrRNA(uiBut *but) +{ + if(but->optype && !but->opptr) { + but->opptr= MEM_callocN(sizeof(PointerRNA), "uiButOpPtr"); + WM_operator_properties_create(but->opptr, but->optype->idname); + } + + return but->opptr; +} + +void uiBlockSetHandleFunc(uiBlock *block, uiBlockHandleFunc func, void *arg) +{ + block->handle_func= func; + block->handle_func_arg= arg; +} + +void uiBlockSetButmFunc(uiBlock *block, uiMenuHandleFunc func, void *arg) +{ + block->butm_func= func; + block->butm_func_arg= arg; +} + +void uiBlockSetFunc(uiBlock *block, uiButHandleFunc func, void *arg1, void *arg2) +{ + block->func= func; + block->func_arg1= arg1; + block->func_arg2= arg2; +} + +void uiBlockSetRenameFunc(uiBlock *block, uiButHandleRenameFunc func, void *arg1) +{ + +} + +void uiBlockSetDrawExtraFunc(uiBlock *block, void (*func)(const bContext *C, void *idv, rcti *rect)) +{ + block->drawextra= func; +} + +void uiButSetFunc(uiBut *but, uiButHandleFunc func, void *arg1, void *arg2) +{ + but->func= func; + but->func_arg1= arg1; + but->func_arg2= arg2; +} + +void uiButSetNFunc(uiBut *but, uiButHandleNFunc funcN, void *argN, void *arg2) +{ + but->funcN= funcN; + but->func_argN= argN; + but->func_arg2= arg2; +} + +void uiButSetCompleteFunc(uiBut *but, uiButCompleteFunc func, void *arg) +{ + but->autocomplete_func= func; + but->autofunc_arg= arg; +} + +uiBut *uiDefIDPoinBut(uiBlock *block, uiIDPoinFuncFP func, short blocktype, int retval, char *str, short x1, short y1, short x2, short y2, void *idpp, char *tip) +{ + uiBut *but= ui_def_but(block, IDPOIN, retval, str, x1, y1, x2, y2, NULL, 0.0, 0.0, 0.0, 0.0, tip); + but->idpoin_func= func; + but->idpoin_idpp= (ID**) idpp; + ui_check_but(but); + + if(blocktype) + uiButSetCompleteFunc(but, autocomplete_id, (void *)(intptr_t)blocktype); + + return but; +} + +uiBut *uiDefBlockBut(uiBlock *block, uiBlockCreateFunc func, void *arg, char *str, short x1, short y1, short x2, short y2, char *tip) +{ + uiBut *but= ui_def_but(block, BLOCK, 0, str, x1, y1, x2, y2, arg, 0.0, 0.0, 0.0, 0.0, tip); + but->block_create_func= func; + ui_check_but(but); + return but; +} + +uiBut *uiDefPulldownBut(uiBlock *block, uiBlockCreateFunc func, void *arg, char *str, short x1, short y1, short x2, short y2, char *tip) +{ + uiBut *but= ui_def_but(block, PULLDOWN, 0, str, x1, y1, x2, y2, arg, 0.0, 0.0, 0.0, 0.0, tip); + but->block_create_func= func; + ui_check_but(but); + return but; +} + +uiBut *uiDefMenuBut(uiBlock *block, uiMenuCreateFunc func, void *arg, char *str, short x1, short y1, short x2, short y2, char *tip) +{ + uiBut *but= ui_def_but(block, PULLDOWN, 0, str, x1, y1, x2, y2, arg, 0.0, 0.0, 0.0, 0.0, tip); + but->menu_create_func= func; + ui_check_but(but); + return but; +} + +uiBut *uiDefIconTextMenuBut(uiBlock *block, uiMenuCreateFunc func, void *arg, int icon, char *str, short x1, short y1, short x2, short y2, char *tip) +{ + uiBut *but= ui_def_but(block, PULLDOWN, 0, str, x1, y1, x2, y2, arg, 0.0, 0.0, 0.0, 0.0, tip); + + but->icon= (BIFIconID) icon; + but->flag|= UI_HAS_ICON; + + but->flag|= UI_ICON_LEFT; + but->flag|= UI_ICON_SUBMENU; + + but->menu_create_func= func; + ui_check_but(but); + + return but; +} + +/* Block button containing both string label and icon */ +uiBut *uiDefIconTextBlockBut(uiBlock *block, uiBlockCreateFunc func, void *arg, int icon, char *str, short x1, short y1, short x2, short y2, char *tip) +{ + uiBut *but= ui_def_but(block, BLOCK, 0, str, x1, y1, x2, y2, arg, 0.0, 0.0, 0.0, 0.0, tip); + + /* XXX temp, old menu calls pass on icon arrow, which is now UI_ICON_SUBMENU flag */ + if(icon!=ICON_RIGHTARROW_THIN) { + but->icon= (BIFIconID) icon; + but->flag|= UI_ICON_LEFT; + } + but->flag|= UI_HAS_ICON; + but->flag|= UI_ICON_SUBMENU; + + but->block_create_func= func; + ui_check_but(but); + + return but; +} + +/* Block button containing icon */ +uiBut *uiDefIconBlockBut(uiBlock *block, uiBlockCreateFunc func, void *arg, int retval, int icon, short x1, short y1, short x2, short y2, char *tip) +{ + uiBut *but= ui_def_but(block, BLOCK, retval, "", x1, y1, x2, y2, arg, 0.0, 0.0, 0.0, 0.0, tip); + + but->icon= (BIFIconID) icon; + but->flag|= UI_HAS_ICON; + + but->flag|= UI_ICON_LEFT; + but->flag|= UI_ICON_SUBMENU; + + but->block_create_func= func; + ui_check_but(but); + + return but; +} + +void uiDefKeyevtButS(uiBlock *block, int retval, char *str, short x1, short y1, short x2, short y2, short *spoin, char *tip) +{ + uiBut *but= ui_def_but(block, KEYEVT|SHO, retval, str, x1, y1, x2, y2, spoin, 0.0, 0.0, 0.0, 0.0, tip); + ui_check_but(but); +} + +/* arg is pointer to string/name, use uiButSetSearchFunc() below to make this work */ +uiBut *uiDefSearchBut(uiBlock *block, void *arg, int retval, int icon, int maxlen, short x1, short y1, short x2, short y2, char *tip) +{ + uiBut *but= ui_def_but(block, SEARCH_MENU, retval, "", x1, y1, x2, y2, arg, 0.0, maxlen, 0.0, 0.0, tip); + + but->icon= (BIFIconID) icon; + but->flag|= UI_HAS_ICON; + + but->flag|= UI_ICON_LEFT|UI_TEXT_LEFT; + but->flag|= UI_ICON_SUBMENU; + + ui_check_but(but); + + return but; +} + +/* arg is user value, searchfunc and handlefunc both get it as arg */ +void uiButSetSearchFunc(uiBut *but, uiButSearchFunc sfunc, void *arg, uiButHandleFunc bfunc) +{ + but->search_func= sfunc; + but->search_arg= arg; + + uiButSetFunc(but, bfunc, arg, NULL); +} + + +/* Program Init/Exit */ + +void UI_init(void) +{ + ui_resources_init(); +} + +/* after reading userdef file */ +void UI_init_userdef(void) +{ + /* fix saved themes */ + init_userdef_do_versions(); + /* set default colors in default theme */ + ui_theme_init_userdef(); + + uiStyleInit(); +} + +void UI_exit(void) +{ + ui_resources_free(); +} + diff --git a/source/blender/editors/interface/interface_anim.c b/source/blender/editors/interface/interface_anim.c new file mode 100644 index 00000000000..4a26db29160 --- /dev/null +++ b/source/blender/editors/interface/interface_anim.c @@ -0,0 +1,172 @@ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "MEM_guardedalloc.h" + +#include "DNA_anim_types.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" + +#include "BLI_listbase.h" + +#include "BKE_animsys.h" +#include "BKE_context.h" +#include "BKE_fcurve.h" + +#include "RNA_access.h" +#include "RNA_types.h" + +#include "UI_interface.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "interface_intern.h" + +void ui_but_anim_flag(uiBut *but, float cfra) +{ + but->flag &= ~(UI_BUT_ANIMATED|UI_BUT_ANIMATED_KEY|UI_BUT_DRIVEN); + + if(but->rnaprop && but->rnapoin.id.data) { + AnimData *adt= BKE_animdata_from_id(but->rnapoin.id.data); + FCurve *fcu; + char *path; + + if (adt) { + if ((adt->action && adt->action->curves.first) || (adt->drivers.first)) { + /* XXX this function call can become a performance bottleneck */ + path= RNA_path_from_ID_to_property(&but->rnapoin, but->rnaprop); + + if (path) { + /* animation takes priority over drivers */ + if (adt->action && adt->action->curves.first) { + fcu= list_find_fcurve(&adt->action->curves, path, but->rnaindex); + + if (fcu) { + but->flag |= UI_BUT_ANIMATED; + + if (on_keyframe_fcurve(fcu, cfra)) + but->flag |= UI_BUT_ANIMATED_KEY; + } + } + + /* if not animated, check if driven */ + if ((but->flag & UI_BUT_ANIMATED)==0 && (adt->drivers.first)) { + fcu= list_find_fcurve(&adt->drivers, path, but->rnaindex); + + if (fcu) + but->flag |= UI_BUT_DRIVEN; + } + + MEM_freeN(path); + } + } + } + } +} + +void uiAnimContextProperty(const bContext *C, struct PointerRNA *ptr, struct PropertyRNA **prop, int *index) +{ + ARegion *ar= CTX_wm_region(C); + uiBlock *block; + uiBut *but; + + if(ar) { + for(block=ar->uiblocks.first; block; block=block->next) { + for(but=block->buttons.first; but; but= but->next) { + if(but->active && but->rnapoin.id.data) { + *ptr= but->rnapoin; + *prop= but->rnaprop; + *index= but->rnaindex; + return; + } + } + } + } +} + +void ui_but_anim_insert_keyframe(bContext *C) +{ + /* this operator calls uiAnimContextProperty above */ + WM_operator_name_call(C, "ANIM_OT_insert_keyframe_button", WM_OP_INVOKE_DEFAULT, NULL); +} + +void ui_but_anim_delete_keyframe(bContext *C) +{ + /* this operator calls uiAnimContextProperty above */ + WM_operator_name_call(C, "ANIM_OT_delete_keyframe_button", WM_OP_INVOKE_DEFAULT, NULL); +} + +void ui_but_anim_add_driver(bContext *C) +{ + /* this operator calls uiAnimContextProperty above */ + WM_operator_name_call(C, "ANIM_OT_add_driver_button", WM_OP_INVOKE_DEFAULT, NULL); +} + +void ui_but_anim_remove_driver(bContext *C) +{ + /* this operator calls uiAnimContextProperty above */ + WM_operator_name_call(C, "ANIM_OT_remove_driver_button", WM_OP_INVOKE_DEFAULT, NULL); +} + +// TODO: refine the logic for adding/removing drivers... +void ui_but_anim_menu(bContext *C, uiBut *but) +{ + uiPopupMenu *pup; + uiLayout *layout; + int length; + + if(but->rnapoin.data && but->rnaprop) { + pup= uiPupMenuBegin(C, RNA_property_ui_name(but->rnaprop), 0); + layout= uiPupMenuLayout(pup); + + length= RNA_property_array_length(but->rnaprop); + + if(but->flag & UI_BUT_ANIMATED_KEY) { + if(length) { + uiItemBooleanO(layout, "Replace Keyframes", 0, "ANIM_OT_insert_keyframe_button", "all", 1); + uiItemBooleanO(layout, "Replace Single Keyframe", 0, "ANIM_OT_insert_keyframe_button", "all", 0); + uiItemBooleanO(layout, "Delete Keyframes", 0, "ANIM_OT_delete_keyframe_button", "all", 1); + uiItemBooleanO(layout, "Delete Single Keyframe", 0, "ANIM_OT_delete_keyframe_button", "all", 0); + } + else { + uiItemBooleanO(layout, "Replace Keyframe", 0, "ANIM_OT_insert_keyframe_button", "all", 0); + uiItemBooleanO(layout, "Delete Keyframe", 0, "ANIM_OT_delete_keyframe_button", "all", 0); + } + } + else if(RNA_property_animateable(&but->rnapoin, but->rnaprop)) { + if(length) { + uiItemBooleanO(layout, "Insert Keyframes", 0, "ANIM_OT_insert_keyframe_button", "all", 1); + uiItemBooleanO(layout, "Insert Single Keyframe", 0, "ANIM_OT_insert_keyframe_button", "all", 0); + } + else + uiItemBooleanO(layout, "Insert Keyframe", 0, "ANIM_OT_insert_keyframe_button", "all", 0); + } + + if(but->flag & UI_BUT_DRIVEN) { + uiItemS(layout); + + if(length) { + uiItemBooleanO(layout, "Remove Driver", 0, "ANIM_OT_remove_driver_button", "all", 1); + uiItemBooleanO(layout, "Remove Single Driver", 0, "ANIM_OT_remove_driver_button", "all", 0); + } + else + uiItemBooleanO(layout, "Remove Driver", 0, "ANIM_OT_remove_driver_button", "all", 0); + } + else if(RNA_property_animateable(&but->rnapoin, but->rnaprop)) { + uiItemS(layout); + + if(length) { + uiItemBooleanO(layout, "Add Driver", 0, "ANIM_OT_add_driver_button", "all", 1); + uiItemBooleanO(layout, "Add Single Driver", 0, "ANIM_OT_add_driver_button", "all", 0); + } + else + uiItemBooleanO(layout, "Add Driver", 0, "ANIM_OT_add_driver_button", "all", 0); + } + + uiPupMenuEnd(C, pup); + } +} + diff --git a/source/blender/editors/interface/interface_api.c b/source/blender/editors/interface/interface_api.c new file mode 100644 index 00000000000..60bfe4e79ad --- /dev/null +++ b/source/blender/editors/interface/interface_api.c @@ -0,0 +1,234 @@ +/** + * $Id: + * + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2009 Blender Foundation. + * All rights reserved. + * + * + * Contributor(s): Blender Foundation + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include <stdlib.h> +#include <stdio.h> + +#include "RNA_define.h" +#include "RNA_types.h" + +#include "UI_interface.h" + +static void api_ui_item_common(FunctionRNA *func) +{ + RNA_def_string(func, "text", "", 0, "", "Override automatic text of the item."); + RNA_def_int(func, "icon", 0, 0, INT_MAX, "", "Override automatic icon of the item.", 0, INT_MAX); +} + +static void api_ui_item_op_common(FunctionRNA *func) +{ + PropertyRNA *parm; + + api_ui_item_common(func); + parm= RNA_def_string(func, "operator", "", 0, "", "Identifier of the operator."); + RNA_def_property_flag(parm, PROP_REQUIRED); +} + +void RNA_api_ui_layout(StructRNA *srna) +{ + FunctionRNA *func; + PropertyRNA *parm; + + static EnumPropertyItem curve_type_items[] = { + {0, "NONE", "None", ""}, + {'v', "VECTOR", "Vector", ""}, + {'c', "COLOR", "Color", ""}, + {0, NULL, NULL, NULL}}; + + /* simple layout specifiers */ + func= RNA_def_function(srna, "row", "uiLayoutRow"); + parm= RNA_def_pointer(func, "layout", "UILayout", "", "Sub-layout to put items in."); + RNA_def_function_return(func, parm); + RNA_def_boolean(func, "align", 0, "", "Align buttons to each other."); + + func= RNA_def_function(srna, "column", "uiLayoutColumn"); + parm= RNA_def_pointer(func, "layout", "UILayout", "", "Sub-layout to put items in."); + RNA_def_function_return(func, parm); + RNA_def_boolean(func, "align", 0, "", "Align buttons to each other."); + + func= RNA_def_function(srna, "column_flow", "uiLayoutColumnFlow"); + parm= RNA_def_int(func, "columns", 0, 0, INT_MAX, "", "Number of columns, 0 is automatic.", 0, INT_MAX); + parm= RNA_def_pointer(func, "layout", "UILayout", "", "Sub-layout to put items in."); + RNA_def_function_return(func, parm); + RNA_def_boolean(func, "align", 0, "", "Align buttons to each other."); + + /* box layout */ + func= RNA_def_function(srna, "box", "uiLayoutBox"); + parm= RNA_def_pointer(func, "layout", "UILayout", "", "Sub-layout to put items in."); + RNA_def_function_return(func, parm); + + /* split layout */ + func= RNA_def_function(srna, "split", "uiLayoutSplit"); + parm= RNA_def_pointer(func, "layout", "UILayout", "", "Sub-layout to put items in."); + RNA_def_function_return(func, parm); + RNA_def_float(func, "percentage", 0.5f, 0.0f, 1.0f, "Percentage", "Percentage of width to split at.", 0.0f, 1.0f); + + /* items */ + func= RNA_def_function(srna, "itemR", "uiItemR"); + api_ui_item_common(func); + parm= RNA_def_pointer(func, "data", "AnyType", "", "Data from which to take property."); + RNA_def_property_flag(parm, PROP_REQUIRED); + parm= RNA_def_string(func, "property", "", 0, "", "Identifier of property in data."); + RNA_def_property_flag(parm, PROP_REQUIRED); + RNA_def_boolean(func, "expand", 0, "", "Expand button to show more detail."); + RNA_def_boolean(func, "slider", 0, "", "Use slider widget for numeric values."); + RNA_def_boolean(func, "toggle", 0, "", "Use toggle widget for boolean values."); + + func= RNA_def_function(srna, "items_enumR", "uiItemsEnumR"); + parm= RNA_def_pointer(func, "data", "AnyType", "", "Data from which to take property."); + RNA_def_property_flag(parm, PROP_REQUIRED); + parm= RNA_def_string(func, "property", "", 0, "", "Identifier of property in data."); + RNA_def_property_flag(parm, PROP_REQUIRED); + + func= RNA_def_function(srna, "item_menu_enumR", "uiItemMenuEnumR"); + api_ui_item_common(func); + parm= RNA_def_pointer(func, "data", "AnyType", "", "Data from which to take property."); + RNA_def_property_flag(parm, PROP_REQUIRED); + parm= RNA_def_string(func, "property", "", 0, "", "Identifier of property in data."); + RNA_def_property_flag(parm, PROP_REQUIRED); + + /*func= RNA_def_function(srna, "item_enumR", "uiItemEnumR"); + api_ui_item_common(func); + parm= RNA_def_pointer(func, "data", "AnyType", "", "Data from which to take property."); + RNA_def_property_flag(parm, PROP_REQUIRED); + parm= RNA_def_string(func, "property", "", 0, "", "Identifier of property in data."); + RNA_def_property_flag(parm, PROP_REQUIRED); + parm= RNA_def_string(func, "value", "", 0, "", "Enum property value."); + RNA_def_property_flag(parm, PROP_REQUIRED);*/ + + func= RNA_def_function(srna, "itemO", "uiItemO"); + api_ui_item_op_common(func); + + func= RNA_def_function(srna, "item_enumO", "uiItemEnumO_string"); + api_ui_item_op_common(func); + parm= RNA_def_string(func, "property", "", 0, "", "Identifier of property in operator."); + RNA_def_property_flag(parm, PROP_REQUIRED); + parm= RNA_def_string(func, "value", "", 0, "", "Enum property value."); + RNA_def_property_flag(parm, PROP_REQUIRED); + + func= RNA_def_function(srna, "items_enumO", "uiItemsEnumO"); + parm= RNA_def_string(func, "operator", "", 0, "", "Identifier of the operator."); + RNA_def_property_flag(parm, PROP_REQUIRED); + parm= RNA_def_string(func, "property", "", 0, "", "Identifier of property in operator."); + RNA_def_property_flag(parm, PROP_REQUIRED); + + func= RNA_def_function(srna, "item_menu_enumO", "uiItemMenuEnumO"); + api_ui_item_op_common(func); + parm= RNA_def_string(func, "property", "", 0, "", "Identifier of property in operator."); + RNA_def_property_flag(parm, PROP_REQUIRED); + + func= RNA_def_function(srna, "item_booleanO", "uiItemBooleanO"); + api_ui_item_op_common(func); + parm= RNA_def_string(func, "property", "", 0, "", "Identifier of property in operator."); + RNA_def_property_flag(parm, PROP_REQUIRED); + parm= RNA_def_boolean(func, "value", 0, "", "Value of the property to call the operator with."); + RNA_def_property_flag(parm, PROP_REQUIRED); + + func= RNA_def_function(srna, "item_intO", "uiItemIntO"); + api_ui_item_op_common(func); + parm= RNA_def_string(func, "property", "", 0, "", "Identifier of property in operator."); + RNA_def_property_flag(parm, PROP_REQUIRED); + parm= RNA_def_int(func, "value", 0, INT_MIN, INT_MAX, "", "Value of the property to call the operator with.", INT_MIN, INT_MAX); + RNA_def_property_flag(parm, PROP_REQUIRED); + + func= RNA_def_function(srna, "item_floatO", "uiItemFloatO"); + api_ui_item_op_common(func); + parm= RNA_def_string(func, "property", "", 0, "", "Identifier of property in operator."); + RNA_def_property_flag(parm, PROP_REQUIRED); + parm= RNA_def_float(func, "value", 0, -FLT_MAX, FLT_MAX, "", "Value of the property to call the operator with.", -FLT_MAX, FLT_MAX); + RNA_def_property_flag(parm, PROP_REQUIRED); + + func= RNA_def_function(srna, "item_stringO", "uiItemStringO"); + api_ui_item_op_common(func); + parm= RNA_def_string(func, "property", "", 0, "", "Identifier of property in operator."); + RNA_def_property_flag(parm, PROP_REQUIRED); + parm= RNA_def_string(func, "value", "", 0, "", "Value of the property to call the operator with."); + RNA_def_property_flag(parm, PROP_REQUIRED); + + func= RNA_def_function(srna, "itemL", "uiItemL"); + api_ui_item_common(func); + + func= RNA_def_function(srna, "itemM", "uiItemM"); + parm= RNA_def_pointer(func, "context", "Context", "", "Current context."); + RNA_def_property_flag(parm, PROP_REQUIRED); + api_ui_item_common(func); + parm= RNA_def_string(func, "menu", "", 0, "", "Identifier of the menu."); + RNA_def_property_flag(parm, PROP_REQUIRED); + + func= RNA_def_function(srna, "itemS", "uiItemS"); + + /* context */ + func= RNA_def_function(srna, "set_context_pointer", "uiLayoutSetContextPointer"); + parm= RNA_def_string(func, "name", "", 0, "Name", "Name of entry in the context."); + RNA_def_property_flag(parm, PROP_REQUIRED); + parm= RNA_def_pointer(func, "data", "AnyType", "", "Pointer to put in context."); + RNA_def_property_flag(parm, PROP_REQUIRED); + + /* templates */ + func= RNA_def_function(srna, "template_header", "uiTemplateHeader"); + parm= RNA_def_pointer(func, "context", "Context", "", "Current context."); + RNA_def_property_flag(parm, PROP_REQUIRED); + + func= RNA_def_function(srna, "template_ID", "uiTemplateID"); + parm= RNA_def_pointer(func, "context", "Context", "", "Current context."); + RNA_def_property_flag(parm, PROP_REQUIRED); + parm= RNA_def_pointer(func, "data", "AnyType", "", "Data from which to take property."); + RNA_def_property_flag(parm, PROP_REQUIRED); + parm= RNA_def_string(func, "property", "", 0, "", "Identifier of pointer property in data."); + RNA_def_property_flag(parm, PROP_REQUIRED); + RNA_def_string(func, "new", "", 0, "", "Operator identifier to create a new ID block."); + RNA_def_string(func, "open", "", 0, "", "Operator identifier to open a new ID block."); + RNA_def_string(func, "unlink", "", 0, "", "Operator identifier to unlink the ID block."); + + func= RNA_def_function(srna, "template_modifier", "uiTemplateModifier"); + parm= RNA_def_pointer(func, "data", "AnyType", "", "Modifier data."); + RNA_def_property_flag(parm, PROP_REQUIRED); + parm= RNA_def_pointer(func, "layout", "UILayout", "", "Sub-layout to put items in."); + RNA_def_function_return(func, parm); + + func= RNA_def_function(srna, "template_constraint", "uiTemplateConstraint"); + parm= RNA_def_pointer(func, "data", "AnyType", "", "Constraint data."); + RNA_def_property_flag(parm, PROP_REQUIRED); + parm= RNA_def_pointer(func, "layout", "UILayout", "", "Sub-layout to put items in."); + RNA_def_function_return(func, parm); + + func= RNA_def_function(srna, "template_preview", "uiTemplatePreview"); + parm= RNA_def_pointer(func, "id", "ID", "", "ID datablock."); + RNA_def_property_flag(parm, PROP_REQUIRED); + + func= RNA_def_function(srna, "template_curve_mapping", "uiTemplateCurveMapping"); + parm= RNA_def_pointer(func, "curvemap", "CurveMapping", "", "Curve mapping pointer."); + RNA_def_property_flag(parm, PROP_REQUIRED); + RNA_def_enum(func, "type", curve_type_items, 0, "Type", "Type of curves to display."); + + func= RNA_def_function(srna, "template_color_ramp", "uiTemplateColorRamp"); + parm= RNA_def_pointer(func, "ramp", "ColorRamp", "", "Color ramp pointer."); + RNA_def_property_flag(parm, PROP_REQUIRED); + RNA_def_boolean(func, "expand", 0, "", "Expand button to show more detail."); +} + diff --git a/source/blender/editors/interface/interface_draw.c b/source/blender/editors/interface/interface_draw.c new file mode 100644 index 00000000000..6d6d4ab9299 --- /dev/null +++ b/source/blender/editors/interface/interface_draw.c @@ -0,0 +1,1133 @@ +/** + * $Id: interface_draw.c 15733 2008-07-24 09:23:13Z aligorith $ + * + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + * + * The Original Code is: all of this file. + * + * Contributor(s): none yet. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include <math.h> +#include <string.h> + +#include "DNA_color_types.h" +#include "DNA_listBase.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" +#include "DNA_texture_types.h" +#include "DNA_userdef_types.h" + +#include "BLI_arithb.h" + +#include "BKE_colortools.h" +#include "BKE_texture.h" +#include "BKE_utildefines.h" + +#include "BIF_gl.h" +#include "BIF_glutil.h" + +#include "UI_interface.h" +#include "UI_interface_icons.h" + +#include "interface_intern.h" + +#define UI_RB_ALPHA 16 +#define UI_DISABLED_ALPHA_OFFS -160 + +static int roundboxtype= 15; + +void uiSetRoundBox(int type) +{ + /* Not sure the roundbox function is the best place to change this + * if this is undone, its not that big a deal, only makes curves edges + * square for the */ + roundboxtype= type; + + /* flags to set which corners will become rounded: + + 1------2 + | | + 8------4 + */ + +} + +int uiGetRoundBox(void) +{ + return roundboxtype; +} + +void gl_round_box(int mode, float minx, float miny, float maxx, float maxy, float rad) +{ + float vec[7][2]= {{0.195, 0.02}, {0.383, 0.067}, {0.55, 0.169}, {0.707, 0.293}, + {0.831, 0.45}, {0.924, 0.617}, {0.98, 0.805}}; + int a; + + /* mult */ + for(a=0; a<7; a++) { + vec[a][0]*= rad; vec[a][1]*= rad; + } + + glBegin(mode); + + /* start with corner right-bottom */ + if(roundboxtype & 4) { + glVertex2f(maxx-rad, miny); + for(a=0; a<7; a++) { + glVertex2f(maxx-rad+vec[a][0], miny+vec[a][1]); + } + glVertex2f(maxx, miny+rad); + } + else glVertex2f(maxx, miny); + + /* corner right-top */ + if(roundboxtype & 2) { + glVertex2f(maxx, maxy-rad); + for(a=0; a<7; a++) { + glVertex2f(maxx-vec[a][1], maxy-rad+vec[a][0]); + } + glVertex2f(maxx-rad, maxy); + } + else glVertex2f(maxx, maxy); + + /* corner left-top */ + if(roundboxtype & 1) { + glVertex2f(minx+rad, maxy); + for(a=0; a<7; a++) { + glVertex2f(minx+rad-vec[a][0], maxy-vec[a][1]); + } + glVertex2f(minx, maxy-rad); + } + else glVertex2f(minx, maxy); + + /* corner left-bottom */ + if(roundboxtype & 8) { + glVertex2f(minx, miny+rad); + for(a=0; a<7; a++) { + glVertex2f(minx+vec[a][1], miny+rad-vec[a][0]); + } + glVertex2f(minx+rad, miny); + } + else glVertex2f(minx, miny); + + glEnd(); +} + +static void round_box_shade_col(float *col1, float *col2, float fac) +{ + float col[3]; + + col[0]= (fac*col1[0] + (1.0-fac)*col2[0]); + col[1]= (fac*col1[1] + (1.0-fac)*col2[1]); + col[2]= (fac*col1[2] + (1.0-fac)*col2[2]); + + glColor3fv(col); +} + + +/* linear horizontal shade within button or in outline */ +/* view2d scrollers use it */ +void gl_round_box_shade(int mode, float minx, float miny, float maxx, float maxy, float rad, float shadetop, float shadedown) +{ + float vec[7][2]= {{0.195, 0.02}, {0.383, 0.067}, {0.55, 0.169}, {0.707, 0.293}, + {0.831, 0.45}, {0.924, 0.617}, {0.98, 0.805}}; + float div= maxy-miny; + float coltop[3], coldown[3], color[4]; + int a; + + /* mult */ + for(a=0; a<7; a++) { + vec[a][0]*= rad; vec[a][1]*= rad; + } + /* get current color, needs to be outside of glBegin/End */ + glGetFloatv(GL_CURRENT_COLOR, color); + + /* 'shade' defines strength of shading */ + coltop[0]= color[0]+shadetop; if(coltop[0]>1.0) coltop[0]= 1.0; + coltop[1]= color[1]+shadetop; if(coltop[1]>1.0) coltop[1]= 1.0; + coltop[2]= color[2]+shadetop; if(coltop[2]>1.0) coltop[2]= 1.0; + coldown[0]= color[0]+shadedown; if(coldown[0]<0.0) coldown[0]= 0.0; + coldown[1]= color[1]+shadedown; if(coldown[1]<0.0) coldown[1]= 0.0; + coldown[2]= color[2]+shadedown; if(coldown[2]<0.0) coldown[2]= 0.0; + + glShadeModel(GL_SMOOTH); + glBegin(mode); + + /* start with corner right-bottom */ + if(roundboxtype & 4) { + + round_box_shade_col(coltop, coldown, 0.0); + glVertex2f(maxx-rad, miny); + + for(a=0; a<7; a++) { + round_box_shade_col(coltop, coldown, vec[a][1]/div); + glVertex2f(maxx-rad+vec[a][0], miny+vec[a][1]); + } + + round_box_shade_col(coltop, coldown, rad/div); + glVertex2f(maxx, miny+rad); + } + else { + round_box_shade_col(coltop, coldown, 0.0); + glVertex2f(maxx, miny); + } + + /* corner right-top */ + if(roundboxtype & 2) { + + round_box_shade_col(coltop, coldown, (div-rad)/div); + glVertex2f(maxx, maxy-rad); + + for(a=0; a<7; a++) { + round_box_shade_col(coltop, coldown, (div-rad+vec[a][1])/div); + glVertex2f(maxx-vec[a][1], maxy-rad+vec[a][0]); + } + round_box_shade_col(coltop, coldown, 1.0); + glVertex2f(maxx-rad, maxy); + } + else { + round_box_shade_col(coltop, coldown, 1.0); + glVertex2f(maxx, maxy); + } + + /* corner left-top */ + if(roundboxtype & 1) { + + round_box_shade_col(coltop, coldown, 1.0); + glVertex2f(minx+rad, maxy); + + for(a=0; a<7; a++) { + round_box_shade_col(coltop, coldown, (div-vec[a][1])/div); + glVertex2f(minx+rad-vec[a][0], maxy-vec[a][1]); + } + + round_box_shade_col(coltop, coldown, (div-rad)/div); + glVertex2f(minx, maxy-rad); + } + else { + round_box_shade_col(coltop, coldown, 1.0); + glVertex2f(minx, maxy); + } + + /* corner left-bottom */ + if(roundboxtype & 8) { + + round_box_shade_col(coltop, coldown, rad/div); + glVertex2f(minx, miny+rad); + + for(a=0; a<7; a++) { + round_box_shade_col(coltop, coldown, (rad-vec[a][1])/div); + glVertex2f(minx+vec[a][1], miny+rad-vec[a][0]); + } + + round_box_shade_col(coltop, coldown, 0.0); + glVertex2f(minx+rad, miny); + } + else { + round_box_shade_col(coltop, coldown, 0.0); + glVertex2f(minx, miny); + } + + glEnd(); + glShadeModel(GL_FLAT); +} + +/* linear vertical shade within button or in outline */ +/* view2d scrollers use it */ +void gl_round_box_vertical_shade(int mode, float minx, float miny, float maxx, float maxy, float rad, float shadeLeft, float shadeRight) +{ + float vec[7][2]= {{0.195, 0.02}, {0.383, 0.067}, {0.55, 0.169}, {0.707, 0.293}, + {0.831, 0.45}, {0.924, 0.617}, {0.98, 0.805}}; + float div= maxx-minx; + float colLeft[3], colRight[3], color[4]; + int a; + + /* mult */ + for(a=0; a<7; a++) { + vec[a][0]*= rad; vec[a][1]*= rad; + } + /* get current color, needs to be outside of glBegin/End */ + glGetFloatv(GL_CURRENT_COLOR, color); + + /* 'shade' defines strength of shading */ + colLeft[0]= color[0]+shadeLeft; if(colLeft[0]>1.0) colLeft[0]= 1.0; + colLeft[1]= color[1]+shadeLeft; if(colLeft[1]>1.0) colLeft[1]= 1.0; + colLeft[2]= color[2]+shadeLeft; if(colLeft[2]>1.0) colLeft[2]= 1.0; + colRight[0]= color[0]+shadeRight; if(colRight[0]<0.0) colRight[0]= 0.0; + colRight[1]= color[1]+shadeRight; if(colRight[1]<0.0) colRight[1]= 0.0; + colRight[2]= color[2]+shadeRight; if(colRight[2]<0.0) colRight[2]= 0.0; + + glShadeModel(GL_SMOOTH); + glBegin(mode); + + /* start with corner right-bottom */ + if(roundboxtype & 4) { + round_box_shade_col(colLeft, colRight, 0.0); + glVertex2f(maxx-rad, miny); + + for(a=0; a<7; a++) { + round_box_shade_col(colLeft, colRight, vec[a][0]/div); + glVertex2f(maxx-rad+vec[a][0], miny+vec[a][1]); + } + + round_box_shade_col(colLeft, colRight, rad/div); + glVertex2f(maxx, miny+rad); + } + else { + round_box_shade_col(colLeft, colRight, 0.0); + glVertex2f(maxx, miny); + } + + /* corner right-top */ + if(roundboxtype & 2) { + round_box_shade_col(colLeft, colRight, 0.0); + glVertex2f(maxx, maxy-rad); + + for(a=0; a<7; a++) { + + round_box_shade_col(colLeft, colRight, (div-rad-vec[a][0])/div); + glVertex2f(maxx-vec[a][1], maxy-rad+vec[a][0]); + } + round_box_shade_col(colLeft, colRight, (div-rad)/div); + glVertex2f(maxx-rad, maxy); + } + else { + round_box_shade_col(colLeft, colRight, 0.0); + glVertex2f(maxx, maxy); + } + + /* corner left-top */ + if(roundboxtype & 1) { + round_box_shade_col(colLeft, colRight, (div-rad)/div); + glVertex2f(minx+rad, maxy); + + for(a=0; a<7; a++) { + round_box_shade_col(colLeft, colRight, (div-rad+vec[a][0])/div); + glVertex2f(minx+rad-vec[a][0], maxy-vec[a][1]); + } + + round_box_shade_col(colLeft, colRight, 1.0); + glVertex2f(minx, maxy-rad); + } + else { + round_box_shade_col(colLeft, colRight, 1.0); + glVertex2f(minx, maxy); + } + + /* corner left-bottom */ + if(roundboxtype & 8) { + round_box_shade_col(colLeft, colRight, 1.0); + glVertex2f(minx, miny+rad); + + for(a=0; a<7; a++) { + round_box_shade_col(colLeft, colRight, (vec[a][0])/div); + glVertex2f(minx+vec[a][1], miny+rad-vec[a][0]); + } + + round_box_shade_col(colLeft, colRight, 1.0); + glVertex2f(minx+rad, miny); + } + else { + round_box_shade_col(colLeft, colRight, 1.0); + glVertex2f(minx, miny); + } + + glEnd(); + glShadeModel(GL_FLAT); +} + +/* plain antialiased unfilled rectangle */ +void uiRoundRect(float minx, float miny, float maxx, float maxy, float rad) +{ + float color[4]; + + if(roundboxtype & UI_RB_ALPHA) { + glGetFloatv(GL_CURRENT_COLOR, color); + color[3]= 0.5; + glColor4fv(color); + glEnable( GL_BLEND ); + } + + /* set antialias line */ + glEnable( GL_LINE_SMOOTH ); + glEnable( GL_BLEND ); + + gl_round_box(GL_LINE_LOOP, minx, miny, maxx, maxy, rad); + + glDisable( GL_BLEND ); + glDisable( GL_LINE_SMOOTH ); +} + +/* plain fake antialiased unfilled round rectangle */ +void uiRoundRectFakeAA(float minx, float miny, float maxx, float maxy, float rad, float asp) +{ + float color[4], alpha; + float raddiff; + int i, passes=4; + + /* get the colour and divide up the alpha */ + glGetFloatv(GL_CURRENT_COLOR, color); + alpha = 1; //color[3]; + color[3]= 0.5*alpha/(float)passes; + glColor4fv(color); + + /* set the 'jitter amount' */ + raddiff = (1/(float)passes) * asp; + + glEnable( GL_BLEND ); + + /* draw lots of lines on top of each other */ + for (i=passes; i>=(-passes); i--) { + gl_round_box(GL_LINE_LOOP, minx, miny, maxx, maxy, rad+(i*raddiff)); + } + + glDisable( GL_BLEND ); + + color[3] = alpha; + glColor4fv(color); +} + +/* (old, used in outliner) plain antialiased filled box */ +void uiRoundBox(float minx, float miny, float maxx, float maxy, float rad) +{ + float color[4]; + + if(roundboxtype & UI_RB_ALPHA) { + glGetFloatv(GL_CURRENT_COLOR, color); + color[3]= 0.5; + glColor4fv(color); + glEnable( GL_BLEND ); + } + + /* solid part */ + gl_round_box(GL_POLYGON, minx, miny, maxx, maxy, rad); + + /* set antialias line */ + glEnable( GL_LINE_SMOOTH ); + glEnable( GL_BLEND ); + + gl_round_box(GL_LINE_LOOP, minx, miny, maxx, maxy, rad); + + glDisable( GL_BLEND ); + glDisable( GL_LINE_SMOOTH ); +} + + +/* ************** generic embossed rect, for window sliders etc ************* */ + + +/* text_draw.c uses this */ +void uiEmboss(float x1, float y1, float x2, float y2, int sel) +{ + + /* below */ + if(sel) glColor3ub(200,200,200); + else glColor3ub(50,50,50); + fdrawline(x1, y1, x2, y1); + + /* right */ + fdrawline(x2, y1, x2, y2); + + /* top */ + if(sel) glColor3ub(50,50,50); + else glColor3ub(200,200,200); + fdrawline(x1, y2, x2, y2); + + /* left */ + fdrawline(x1, y1, x1, y2); + +} + +/* ************** TEXT AND ICON DRAWING FUNCTIONS ************* */ + + +#if 0 +#ifdef INTERNATIONAL +static void ui_draw_but_CHARTAB(uiBut *but) +{ + /* XXX 2.50 bad global access */ + /* Some local variables */ + float sx, sy, ex, ey; + float width, height; + float butw, buth; + int x, y, cs; + wchar_t wstr[2]; + unsigned char ustr[16]; + PackedFile *pf; + int result = 0; + int charmax = G.charmax; + + /* <builtin> font in use. There are TTF <builtin> and non-TTF <builtin> fonts */ + if(!strcmp(G.selfont->name, "<builtin>")) + { + if(G.ui_international == TRUE) + { + charmax = 0xff; + } + else + { + charmax = 0xff; + } + } + + /* Category list exited without selecting the area */ + if(G.charmax == 0) + charmax = G.charmax = 0xffff; + + /* Calculate the size of the button */ + width = abs(rect->xmax - rect->xmin); + height = abs(rect->ymax - rect->ymin); + + butw = floor(width / 12); + buth = floor(height / 6); + + /* Initialize variables */ + sx = rect->xmin; + ex = rect->xmin + butw; + sy = rect->ymin + height - buth; + ey = rect->ymin + height; + + cs = G.charstart; + + /* Set the font, in case it is not <builtin> font */ + if(G.selfont && strcmp(G.selfont->name, "<builtin>")) + { + char tmpStr[256]; + + // Is the font file packed, if so then use the packed file + if(G.selfont->packedfile) + { + pf = G.selfont->packedfile; + FTF_SetFont(pf->data, pf->size, 14.0); + } + else + { + int err; + + strcpy(tmpStr, G.selfont->name); + BLI_convertstringcode(tmpStr, G.sce); + err = FTF_SetFont((unsigned char *)tmpStr, 0, 14.0); + } + } + else + { + if(G.ui_international == TRUE) + { + FTF_SetFont((unsigned char *) datatoc_bfont_ttf, datatoc_bfont_ttf_size, 14.0); + } + } + + /* Start drawing the button itself */ + glShadeModel(GL_SMOOTH); + + glColor3ub(200, 200, 200); + glRectf((rect->xmin), (rect->ymin), (rect->xmax), (rect->ymax)); + + glColor3ub(0, 0, 0); + for(y = 0; y < 6; y++) + { + // Do not draw more than the category allows + if(cs > charmax) break; + + for(x = 0; x < 12; x++) + { + // Do not draw more than the category allows + if(cs > charmax) break; + + // Draw one grid cell + glBegin(GL_LINE_LOOP); + glVertex2f(sx, sy); + glVertex2f(ex, sy); + glVertex2f(ex, ey); + glVertex2f(sx, ey); + glEnd(); + + // Draw character inside the cell + memset(wstr, 0, sizeof(wchar_t)*2); + memset(ustr, 0, 16); + + // Set the font to be either unicode or <builtin> + wstr[0] = cs; + if(strcmp(G.selfont->name, "<builtin>")) + { + wcs2utf8s((char *)ustr, (wchar_t *)wstr); + } + else + { + if(G.ui_international == TRUE) + { + wcs2utf8s((char *)ustr, (wchar_t *)wstr); + } + else + { + ustr[0] = cs; + ustr[1] = 0; + } + } + + if((G.selfont && strcmp(G.selfont->name, "<builtin>")) || (G.selfont && !strcmp(G.selfont->name, "<builtin>") && G.ui_international == TRUE)) + { + float wid; + float llx, lly, llz, urx, ury, urz; + float dx, dy; + float px, py; + + // Calculate the position + wid = FTF_GetStringWidth((char *) ustr, FTF_USE_GETTEXT | FTF_INPUT_UTF8); + FTF_GetBoundingBox((char *) ustr, &llx,&lly,&llz,&urx,&ury,&urz, FTF_USE_GETTEXT | FTF_INPUT_UTF8); + dx = urx-llx; + dy = ury-lly; + + // This isn't fully functional since the but->aspect isn't working like I suspected + px = sx + ((butw/but->aspect)-dx)/2; + py = sy + ((buth/but->aspect)-dy)/2; + + // Set the position and draw the character + ui_rasterpos_safe(px, py, but->aspect); + FTF_DrawString((char *) ustr, FTF_USE_GETTEXT | FTF_INPUT_UTF8); + } + else + { + ui_rasterpos_safe(sx + butw/2, sy + buth/2, but->aspect); + UI_DrawString(but->font, (char *) ustr, 0); + } + + // Calculate the next position and character + sx += butw; ex +=butw; + cs++; + } + /* Add the y position and reset x position */ + sy -= buth; + ey -= buth; + sx = rect->xmin; + ex = rect->xmin + butw; + } + glShadeModel(GL_FLAT); + + /* Return Font Settings to original */ + if(U.fontsize && U.fontname[0]) + { + result = FTF_SetFont((unsigned char *)U.fontname, 0, U.fontsize); + } + else if (U.fontsize) + { + result = FTF_SetFont((unsigned char *) datatoc_bfont_ttf, datatoc_bfont_ttf_size, U.fontsize); + } + + if (result == 0) + { + result = FTF_SetFont((unsigned char *) datatoc_bfont_ttf, datatoc_bfont_ttf_size, 11); + } + + /* resets the font size */ + if(G.ui_international == TRUE) + { + // uiSetCurFont(but->block, UI_HELV); + } +} + +#endif // INTERNATIONAL +#endif + +void ui_draw_but_COLORBAND(uiBut *but, uiWidgetColors *wcol, rcti *rect) +{ + ColorBand *coba; + CBData *cbd; + float x1, y1, sizex, sizey; + float dx, v3[2], v1[2], v2[2], v1a[2], v2a[2]; + int a; + + coba= (ColorBand *)(but->editcoba? but->editcoba: but->poin); + if(coba==NULL) return; + + x1= rect->xmin; + y1= rect->ymin; + sizex= rect->xmax-x1; + sizey= rect->ymax-y1; + + /* first background, to show tranparency */ + dx= sizex/12.0; + v1[0]= x1; + for(a=0; a<12; a++) { + if(a & 1) glColor3f(0.3, 0.3, 0.3); else glColor3f(0.8, 0.8, 0.8); + glRectf(v1[0], y1, v1[0]+dx, y1+0.5*sizey); + if(a & 1) glColor3f(0.8, 0.8, 0.8); else glColor3f(0.3, 0.3, 0.3); + glRectf(v1[0], y1+0.5*sizey, v1[0]+dx, y1+sizey); + v1[0]+= dx; + } + + glShadeModel(GL_SMOOTH); + glEnable(GL_BLEND); + + cbd= coba->data; + + v1[0]= v2[0]= x1; + v1[1]= y1; + v2[1]= y1+sizey; + + glBegin(GL_QUAD_STRIP); + + glColor4fv( &cbd->r ); + glVertex2fv(v1); glVertex2fv(v2); + + for(a=0; a<coba->tot; a++, cbd++) { + + v1[0]=v2[0]= x1+ cbd->pos*sizex; + + glColor4fv( &cbd->r ); + glVertex2fv(v1); glVertex2fv(v2); + } + + v1[0]=v2[0]= x1+ sizex; + glVertex2fv(v1); glVertex2fv(v2); + + glEnd(); + glShadeModel(GL_FLAT); + glDisable(GL_BLEND); + + /* outline */ + v1[0]= x1; v1[1]= y1; + + cpack(0x0); + glBegin(GL_LINE_LOOP); + glVertex2fv(v1); + v1[0]+= sizex; + glVertex2fv(v1); + v1[1]+= sizey; + glVertex2fv(v1); + v1[0]-= sizex; + glVertex2fv(v1); + glEnd(); + + + /* help lines */ + v1[0]= v2[0]=v3[0]= x1; + v1[1]= y1; + v1a[1]= y1+0.25*sizey; + v2[1]= y1+0.5*sizey; + v2a[1]= y1+0.75*sizey; + v3[1]= y1+sizey; + + + cbd= coba->data; + glBegin(GL_LINES); + for(a=0; a<coba->tot; a++, cbd++) { + v1[0]=v2[0]=v3[0]=v1a[0]=v2a[0]= x1+ cbd->pos*sizex; + + if(a==coba->cur) { + glColor3ub(0, 0, 0); + glVertex2fv(v1); + glVertex2fv(v3); + glEnd(); + + setlinestyle(2); + glBegin(GL_LINES); + glColor3ub(255, 255, 255); + glVertex2fv(v1); + glVertex2fv(v3); + glEnd(); + setlinestyle(0); + glBegin(GL_LINES); + + /* glColor3ub(0, 0, 0); + glVertex2fv(v1); + glVertex2fv(v1a); + glColor3ub(255, 255, 255); + glVertex2fv(v1a); + glVertex2fv(v2); + glColor3ub(0, 0, 0); + glVertex2fv(v2); + glVertex2fv(v2a); + glColor3ub(255, 255, 255); + glVertex2fv(v2a); + glVertex2fv(v3); + */ + } + else { + glColor3ub(0, 0, 0); + glVertex2fv(v1); + glVertex2fv(v2); + + glColor3ub(255, 255, 255); + glVertex2fv(v2); + glVertex2fv(v3); + } + } + glEnd(); +} + +void ui_draw_but_NORMAL(uiBut *but, uiWidgetColors *wcol, rcti *rect) +{ + static GLuint displist=0; + int a, old[8]; + GLfloat diff[4], diffn[4]={1.0f, 1.0f, 1.0f, 1.0f}; + float vec0[4]={0.0f, 0.0f, 0.0f, 0.0f}; + float dir[4], size; + + /* store stuff */ + glGetMaterialfv(GL_FRONT, GL_DIFFUSE, diff); + + /* backdrop */ + glColor3ubv(wcol->inner); + uiSetRoundBox(15); + gl_round_box(GL_POLYGON, rect->xmin, rect->ymin, rect->xmax, rect->ymax, 5.0f); + + /* sphere color */ + glMaterialfv(GL_FRONT, GL_DIFFUSE, diffn); + glCullFace(GL_BACK); glEnable(GL_CULL_FACE); + + /* disable blender light */ + for(a=0; a<8; a++) { + old[a]= glIsEnabled(GL_LIGHT0+a); + glDisable(GL_LIGHT0+a); + } + + /* own light */ + glEnable(GL_LIGHT7); + glEnable(GL_LIGHTING); + + ui_get_but_vectorf(but, dir); + + dir[3]= 0.0f; /* glLight needs 4 args, 0.0 is sun */ + glLightfv(GL_LIGHT7, GL_POSITION, dir); + glLightfv(GL_LIGHT7, GL_DIFFUSE, diffn); + glLightfv(GL_LIGHT7, GL_SPECULAR, vec0); + glLightf(GL_LIGHT7, GL_CONSTANT_ATTENUATION, 1.0f); + glLightf(GL_LIGHT7, GL_LINEAR_ATTENUATION, 0.0f); + + /* transform to button */ + glPushMatrix(); + glTranslatef(rect->xmin + 0.5f*(rect->xmax-rect->xmin), rect->ymin+ 0.5f*(rect->ymax-rect->ymin), 0.0f); + + if( rect->xmax-rect->xmin < rect->ymax-rect->ymin) + size= (rect->xmax-rect->xmin)/200.f; + else + size= (rect->ymax-rect->ymin)/200.f; + + glScalef(size, size, size); + + if(displist==0) { + GLUquadricObj *qobj; + + displist= glGenLists(1); + glNewList(displist, GL_COMPILE_AND_EXECUTE); + + qobj= gluNewQuadric(); + gluQuadricDrawStyle(qobj, GLU_FILL); + glShadeModel(GL_SMOOTH); + gluSphere( qobj, 100.0, 32, 24); + glShadeModel(GL_FLAT); + gluDeleteQuadric(qobj); + + glEndList(); + } + else glCallList(displist); + + /* restore */ + glDisable(GL_LIGHTING); + glDisable(GL_CULL_FACE); + glMaterialfv(GL_FRONT, GL_DIFFUSE, diff); + glDisable(GL_LIGHT7); + + /* AA circle */ + glEnable(GL_BLEND); + glEnable(GL_LINE_SMOOTH ); + glColor3ubv(wcol->inner); + glutil_draw_lined_arc(0.0f, M_PI*2.0, 100.0f, 32); + glDisable(GL_BLEND); + glDisable(GL_LINE_SMOOTH ); + + /* matrix after circle */ + glPopMatrix(); + + /* enable blender light */ + for(a=0; a<8; a++) { + if(old[a]) + glEnable(GL_LIGHT0+a); + } +} + +static void ui_draw_but_curve_grid(rcti *rect, float zoomx, float zoomy, float offsx, float offsy, float step) +{ + float dx, dy, fx, fy; + + glBegin(GL_LINES); + dx= step*zoomx; + fx= rect->xmin + zoomx*(-offsx); + if(fx > rect->xmin) fx -= dx*( floor(fx-rect->xmin)); + while(fx < rect->xmax) { + glVertex2f(fx, rect->ymin); + glVertex2f(fx, rect->ymax); + fx+= dx; + } + + dy= step*zoomy; + fy= rect->ymin + zoomy*(-offsy); + if(fy > rect->ymin) fy -= dy*( floor(fy-rect->ymin)); + while(fy < rect->ymax) { + glVertex2f(rect->xmin, fy); + glVertex2f(rect->xmax, fy); + fy+= dy; + } + glEnd(); + +} + +static void glColor3ubvShade(char *col, int shade) +{ + glColor3ub(col[0]-shade>0?col[0]-shade:0, + col[1]-shade>0?col[1]-shade:0, + col[2]-shade>0?col[2]-shade:0); +} + +void ui_draw_but_CURVE(ARegion *ar, uiBut *but, uiWidgetColors *wcol, rcti *rect) +{ + CurveMapping *cumap; + CurveMap *cuma; + CurveMapPoint *cmp; + float fx, fy, fac[2], zoomx, zoomy, offsx, offsy; + GLint scissor[4]; + int a; + + cumap= (CurveMapping *)(but->editcumap? but->editcumap: but->poin); + cuma= cumap->cm+cumap->cur; + + /* need scissor test, curve can draw outside of boundary */ + glGetIntegerv(GL_VIEWPORT, scissor); + glScissor(ar->winrct.xmin + rect->xmin, ar->winrct.ymin+rect->ymin, rect->xmax-rect->xmin, rect->ymax-rect->ymin); + + /* calculate offset and zoom */ + zoomx= (rect->xmax-rect->xmin-2.0*but->aspect)/(cumap->curr.xmax - cumap->curr.xmin); + zoomy= (rect->ymax-rect->ymin-2.0*but->aspect)/(cumap->curr.ymax - cumap->curr.ymin); + offsx= cumap->curr.xmin-but->aspect/zoomx; + offsy= cumap->curr.ymin-but->aspect/zoomy; + + /* backdrop */ + if(cumap->flag & CUMA_DO_CLIP) { + glColor3ubvShade(wcol->inner, -20); + glRectf(rect->xmin, rect->ymin, rect->xmax, rect->ymax); + glColor3ubv(wcol->inner); + glRectf(rect->xmin + zoomx*(cumap->clipr.xmin-offsx), + rect->ymin + zoomy*(cumap->clipr.ymin-offsy), + rect->xmin + zoomx*(cumap->clipr.xmax-offsx), + rect->ymin + zoomy*(cumap->clipr.ymax-offsy)); + } + else { + glColor3ubv(wcol->inner); + glRectf(rect->xmin, rect->ymin, rect->xmax, rect->ymax); + } + + /* grid, every .25 step */ + glColor3ubvShade(wcol->inner, -16); + ui_draw_but_curve_grid(rect, zoomx, zoomy, offsx, offsy, 0.25f); + /* grid, every 1.0 step */ + glColor3ubvShade(wcol->inner, -24); + ui_draw_but_curve_grid(rect, zoomx, zoomy, offsx, offsy, 1.0f); + /* axes */ + glColor3ubvShade(wcol->inner, -50); + glBegin(GL_LINES); + glVertex2f(rect->xmin, rect->ymin + zoomy*(-offsy)); + glVertex2f(rect->xmax, rect->ymin + zoomy*(-offsy)); + glVertex2f(rect->xmin + zoomx*(-offsx), rect->ymin); + glVertex2f(rect->xmin + zoomx*(-offsx), rect->ymax); + glEnd(); + + /* cfra option */ + /* XXX 2.48 + if(cumap->flag & CUMA_DRAW_CFRA) { + glColor3ub(0x60, 0xc0, 0x40); + glBegin(GL_LINES); + glVertex2f(rect->xmin + zoomx*(cumap->sample[0]-offsx), rect->ymin); + glVertex2f(rect->xmin + zoomx*(cumap->sample[0]-offsx), rect->ymax); + glEnd(); + }*/ + /* sample option */ + /* XXX 2.48 + * if(cumap->flag & CUMA_DRAW_SAMPLE) { + if(cumap->cur==3) { + float lum= cumap->sample[0]*0.35f + cumap->sample[1]*0.45f + cumap->sample[2]*0.2f; + glColor3ub(240, 240, 240); + + glBegin(GL_LINES); + glVertex2f(rect->xmin + zoomx*(lum-offsx), rect->ymin); + glVertex2f(rect->xmin + zoomx*(lum-offsx), rect->ymax); + glEnd(); + } + else { + if(cumap->cur==0) + glColor3ub(240, 100, 100); + else if(cumap->cur==1) + glColor3ub(100, 240, 100); + else + glColor3ub(100, 100, 240); + + glBegin(GL_LINES); + glVertex2f(rect->xmin + zoomx*(cumap->sample[cumap->cur]-offsx), rect->ymin); + glVertex2f(rect->xmin + zoomx*(cumap->sample[cumap->cur]-offsx), rect->ymax); + glEnd(); + } + }*/ + + /* the curve */ + glColor3ubv(wcol->item); + glEnable(GL_LINE_SMOOTH); + glEnable(GL_BLEND); + glBegin(GL_LINE_STRIP); + + if(cuma->table==NULL) + curvemapping_changed(cumap, 0); /* 0 = no remove doubles */ + cmp= cuma->table; + + /* first point */ + if((cuma->flag & CUMA_EXTEND_EXTRAPOLATE)==0) + glVertex2f(rect->xmin, rect->ymin + zoomy*(cmp[0].y-offsy)); + else { + fx= rect->xmin + zoomx*(cmp[0].x-offsx + cuma->ext_in[0]); + fy= rect->ymin + zoomy*(cmp[0].y-offsy + cuma->ext_in[1]); + glVertex2f(fx, fy); + } + for(a=0; a<=CM_TABLE; a++) { + fx= rect->xmin + zoomx*(cmp[a].x-offsx); + fy= rect->ymin + zoomy*(cmp[a].y-offsy); + glVertex2f(fx, fy); + } + /* last point */ + if((cuma->flag & CUMA_EXTEND_EXTRAPOLATE)==0) + glVertex2f(rect->xmax, rect->ymin + zoomy*(cmp[CM_TABLE].y-offsy)); + else { + fx= rect->xmin + zoomx*(cmp[CM_TABLE].x-offsx - cuma->ext_out[0]); + fy= rect->ymin + zoomy*(cmp[CM_TABLE].y-offsy - cuma->ext_out[1]); + glVertex2f(fx, fy); + } + glEnd(); + glDisable(GL_LINE_SMOOTH); + glDisable(GL_BLEND); + + /* the points, use aspect to make them visible on edges */ + cmp= cuma->curve; + glPointSize(3.0f); + bglBegin(GL_POINTS); + for(a=0; a<cuma->totpoint; a++) { + if(cmp[a].flag & SELECT) + UI_ThemeColor(TH_TEXT_HI); + else + UI_ThemeColor(TH_TEXT); + fac[0]= rect->xmin + zoomx*(cmp[a].x-offsx); + fac[1]= rect->ymin + zoomy*(cmp[a].y-offsy); + bglVertex2fv(fac); + } + bglEnd(); + glPointSize(1.0f); + + /* restore scissortest */ + glScissor(scissor[0], scissor[1], scissor[2], scissor[3]); + + /* outline */ + glColor3ubv(wcol->outline); + fdrawbox(rect->xmin, rect->ymin, rect->xmax, rect->ymax); +} + + +/* ****************************************************** */ + + +static void ui_shadowbox(float minx, float miny, float maxx, float maxy, float shadsize, unsigned char alpha) +{ + glEnable(GL_BLEND); + glShadeModel(GL_SMOOTH); + + /* right quad */ + glBegin(GL_POLYGON); + glColor4ub(0, 0, 0, alpha); + glVertex2f(maxx, miny); + glVertex2f(maxx, maxy-0.3*shadsize); + glColor4ub(0, 0, 0, 0); + glVertex2f(maxx+shadsize, maxy-0.75*shadsize); + glVertex2f(maxx+shadsize, miny); + glEnd(); + + /* corner shape */ + glBegin(GL_POLYGON); + glColor4ub(0, 0, 0, alpha); + glVertex2f(maxx, miny); + glColor4ub(0, 0, 0, 0); + glVertex2f(maxx+shadsize, miny); + glVertex2f(maxx+0.7*shadsize, miny-0.7*shadsize); + glVertex2f(maxx, miny-shadsize); + glEnd(); + + /* bottom quad */ + glBegin(GL_POLYGON); + glColor4ub(0, 0, 0, alpha); + glVertex2f(minx+0.3*shadsize, miny); + glVertex2f(maxx, miny); + glColor4ub(0, 0, 0, 0); + glVertex2f(maxx, miny-shadsize); + glVertex2f(minx+0.5*shadsize, miny-shadsize); + glEnd(); + + glDisable(GL_BLEND); + glShadeModel(GL_FLAT); +} + +void uiDrawBoxShadow(unsigned char alpha, float minx, float miny, float maxx, float maxy) +{ + /* accumulated outline boxes to make shade not linear, is more pleasant */ + ui_shadowbox(minx, miny, maxx, maxy, 11.0, (20*alpha)>>8); + ui_shadowbox(minx, miny, maxx, maxy, 7.0, (40*alpha)>>8); + ui_shadowbox(minx, miny, maxx, maxy, 5.0, (80*alpha)>>8); + +} + + +void ui_dropshadow(rctf *rct, float radius, float aspect, int select) +{ + float rad; + float a; + char alpha= 2; + + glEnable(GL_BLEND); + + if(radius > (rct->ymax-rct->ymin-10.0f)/2.0f) + rad= (rct->ymax-rct->ymin-10.0f)/2.0f; + else + rad= radius; + + if(select) a= 12.0f*aspect; else a= 12.0f*aspect; + for(; a>0.0f; a-=aspect) { + /* alpha ranges from 2 to 20 or so */ + glColor4ub(0, 0, 0, alpha); + alpha+= 2; + + gl_round_box(GL_POLYGON, rct->xmin - a, rct->ymin - a, rct->xmax + a, rct->ymax-10.0f + a, rad+a); + } + + /* outline emphasis */ + glEnable( GL_LINE_SMOOTH ); + glColor4ub(0, 0, 0, 100); + gl_round_box(GL_LINE_LOOP, rct->xmin-0.5f, rct->ymin-0.5f, rct->xmax+0.5f, rct->ymax+0.5f, radius); + glDisable( GL_LINE_SMOOTH ); + + glDisable(GL_BLEND); +} + diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c new file mode 100644 index 00000000000..b9e4b18761f --- /dev/null +++ b/source/blender/editors/interface/interface_handlers.c @@ -0,0 +1,3977 @@ +/** + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2008 Blender Foundation. + * All rights reserved. + * + * Contributor(s): Blender Foundation + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include <float.h> +#include <math.h> +#include <stdlib.h> +#include <string.h> + +#include "MEM_guardedalloc.h" + +#include "DNA_color_types.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" +#include "DNA_texture_types.h" +#include "DNA_userdef_types.h" +#include "DNA_windowmanager_types.h" + +#include "BLI_arithb.h" +#include "BLI_blenlib.h" +#include "PIL_time.h" + +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_idprop.h" +#include "BKE_report.h" +#include "BKE_texture.h" +#include "BKE_utildefines.h" + +#include "ED_screen.h" + +#include "UI_interface.h" + +#include "BLF_api.h" + +#include "interface_intern.h" + +#include "RNA_access.h" + +#include "WM_api.h" +#include "WM_types.h" + +/***************** structs and defines ****************/ + +#define BUTTON_TOOLTIP_DELAY 0.500 +#define BUTTON_FLASH_DELAY 0.020 +#define BUTTON_AUTO_OPEN_THRESH 0.3 +#define BUTTON_MOUSE_TOWARDS_THRESH 1.0 + +typedef enum uiButtonActivateType { + BUTTON_ACTIVATE_OVER, + BUTTON_ACTIVATE, + BUTTON_ACTIVATE_APPLY, + BUTTON_ACTIVATE_TEXT_EDITING, + BUTTON_ACTIVATE_OPEN +} uiButtonActivateType; + +typedef enum uiHandleButtonState { + BUTTON_STATE_INIT, + BUTTON_STATE_HIGHLIGHT, + BUTTON_STATE_WAIT_FLASH, + BUTTON_STATE_WAIT_RELEASE, + BUTTON_STATE_WAIT_KEY_EVENT, + BUTTON_STATE_NUM_EDITING, + BUTTON_STATE_TEXT_EDITING, + BUTTON_STATE_TEXT_SELECTING, + BUTTON_STATE_MENU_OPEN, + BUTTON_STATE_EXIT +} uiHandleButtonState; + +typedef struct uiHandleButtonData { + wmWindow *window; + ARegion *region; + + int interactive; + + /* overall state */ + uiHandleButtonState state; + int cancel, escapecancel, retval; + int applied, appliedinteractive; + wmTimer *flashtimer; + + /* edited value */ + char *str, *origstr; + double value, origvalue, startvalue; + float vec[3], origvec[3]; + int togdual, togonly; + ColorBand *coba; + CurveMapping *cumap; + + /* tooltip */ + ARegion *tooltip; + wmTimer *tooltiptimer; + wmTimer *autoopentimer; + + /* text selection/editing */ + int maxlen, selextend, selstartx; + + /* number editing / dragging */ + int draglastx, draglasty; + int dragstartx, dragstarty; + int dragchange, draglock, dragsel; + float dragf, dragfstart; + CBData *dragcbd; + + /* menu open */ + uiPopupBlockHandle *menu; + int menuretval; + + /* search box */ + ARegion *searchbox; + + /* post activate */ + uiButtonActivateType posttype; + uiBut *postbut; +} uiHandleButtonData; + +typedef struct uiAfterFunc { + struct uiAfterFunc *next, *prev; + + uiButHandleFunc func; + void *func_arg1; + void *func_arg2; + void *func_arg3; + + uiButHandleNFunc funcN; + void *func_argN; + + uiBlockHandleFunc handle_func; + void *handle_func_arg; + int retval; + + uiMenuHandleFunc butm_func; + void *butm_func_arg; + int a2; + + wmOperatorType *optype; + int opcontext; + PointerRNA *opptr; + + PointerRNA rnapoin; + PropertyRNA *rnaprop; + + bContextStore *context; +} uiAfterFunc; + +static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState state); +static int ui_handler_region_menu(bContext *C, wmEvent *event, void *userdata); +static int ui_handler_popup(bContext *C, wmEvent *event, void *userdata); +static void ui_handler_remove_popup(bContext *C, void *userdata); +static void ui_handle_button_activate(bContext *C, ARegion *ar, uiBut *but, uiButtonActivateType type); + +/* ******************** menu navigation helpers ************** */ + +static uiBut *ui_but_prev(uiBut *but) +{ + while(but->prev) { + but= but->prev; + if(but->type!=LABEL && but->type!=SEPR && but->type!=ROUNDBOX) return but; + } + return NULL; +} + +static uiBut *ui_but_next(uiBut *but) +{ + while(but->next) { + but= but->next; + if(but->type!=LABEL && but->type!=SEPR && but->type!=ROUNDBOX) return but; + } + return NULL; +} + +static uiBut *ui_but_first(uiBlock *block) +{ + uiBut *but; + + but= block->buttons.first; + while(but) { + if(but->type!=LABEL && but->type!=SEPR && but->type!=ROUNDBOX) return but; + but= but->next; + } + return NULL; +} + +static uiBut *ui_but_last(uiBlock *block) +{ + uiBut *but; + + but= block->buttons.last; + while(but) { + if(but->type!=LABEL && but->type!=SEPR && but->type!=ROUNDBOX) return but; + but= but->prev; + } + return NULL; +} + +/* ********************** button apply/revert ************************/ + +static ListBase UIAfterFuncs = {NULL, NULL}; + +static void ui_apply_but_func(bContext *C, uiBut *but) +{ + uiAfterFunc *after; + uiBlock *block= but->block; + + /* these functions are postponed and only executed after all other + * handling is done, i.e. menus are closed, in order to avoid conflicts + * with these functions removing the buttons we are working with */ + + if(but->func || but->funcN || block->handle_func || (but->type == BUTM && block->butm_func) || but->optype || but->rnaprop) { + after= MEM_callocN(sizeof(uiAfterFunc), "uiAfterFunc"); + + after->func= but->func; + after->func_arg1= but->func_arg1; + after->func_arg2= but->func_arg2; + after->func_arg3= but->func_arg3; + + after->funcN= but->funcN; + after->func_argN= but->func_argN; + + after->handle_func= block->handle_func; + after->handle_func_arg= block->handle_func_arg; + after->retval= but->retval; + + if(but->type == BUTM) { + after->butm_func= block->butm_func; + after->butm_func_arg= block->butm_func_arg; + after->a2= but->a2; + } + + after->optype= but->optype; + after->opcontext= but->opcontext; + after->opptr= but->opptr; + + after->rnapoin= but->rnapoin; + after->rnaprop= but->rnaprop; + + if(but->context) + after->context= CTX_store_copy(but->context); + + but->optype= NULL; + but->opcontext= 0; + but->opptr= NULL; + + BLI_addtail(&UIAfterFuncs, after); + } +} + +static void ui_apply_but_funcs_after(bContext *C) +{ + uiAfterFunc *afterf, after; + ListBase funcs; + + /* copy to avoid recursive calls */ + funcs= UIAfterFuncs; + UIAfterFuncs.first= UIAfterFuncs.last= NULL; + + for(afterf=funcs.first; afterf; afterf=after.next) { + after= *afterf; /* copy to avoid memleak on exit() */ + BLI_freelinkN(&funcs, afterf); + + if(after.context) + CTX_store_set(C, after.context); + + if(after.func) + after.func(C, after.func_arg1, after.func_arg2); + if(after.funcN) + after.funcN(C, after.func_argN, after.func_arg2); + + if(after.handle_func) + after.handle_func(C, after.handle_func_arg, after.retval); + if(after.butm_func) + after.butm_func(C, after.butm_func_arg, after.a2); + + if(after.optype) + WM_operator_name_call(C, after.optype->idname, after.opcontext, after.opptr); + if(after.opptr) { + WM_operator_properties_free(after.opptr); + MEM_freeN(after.opptr); + } + + if(after.rnapoin.data) + RNA_property_update(C, &after.rnapoin, after.rnaprop); + + if(after.context) { + CTX_store_set(C, NULL); + CTX_store_free(after.context); + } + } +} + +static void ui_apply_but_BUT(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + ui_apply_but_func(C, but); + + data->retval= but->retval; + data->applied= 1; +} + +static void ui_apply_but_BUTM(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + ui_set_but_val(but, but->hardmin); + ui_apply_but_func(C, but); + + data->retval= but->retval; + data->applied= 1; +} + +static void ui_apply_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + if(but->type == COL) { + if(but->a1 != -1) // this is not a color picker (weak!) + ui_set_but_vectorf(but, data->vec); + } + else if(ELEM3(but->type, MENU, ICONROW, ICONTEXTROW)) + ui_set_but_val(but, data->value); + + ui_check_but(but); + ui_apply_but_func(C, but); + data->retval= but->retval; + data->applied= 1; +} + +static void ui_apply_but_TOG(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data) +{ + double value; + int w, lvalue, push; + + /* local hack... */ + if(but->type==BUT_TOGDUAL && data->togdual) { + if(but->pointype==SHO) + but->poin += 2; + else if(but->pointype==INT) + but->poin += 4; + } + + value= ui_get_but_val(but); + lvalue= (int)value; + + if(but->bit) { + w= BTST(lvalue, but->bitnr); + if(w) lvalue = BCLR(lvalue, but->bitnr); + else lvalue = BSET(lvalue, but->bitnr); + + if(but->type==TOGR) { + if(!data->togonly) { + lvalue= 1<<(but->bitnr); + + ui_set_but_val(but, (double)lvalue); + } + else { + if(lvalue==0) lvalue= 1<<(but->bitnr); + } + } + + ui_set_but_val(but, (double)lvalue); + if(but->type==ICONTOG || but->type==ICONTOGN) ui_check_but(but); + } + else { + + if(value==0.0) push= 1; + else push= 0; + + if(ELEM3(but->type, TOGN, ICONTOGN, OPTIONN)) push= !push; + ui_set_but_val(but, (double)push); + if(but->type==ICONTOG || but->type==ICONTOGN) ui_check_but(but); + } + + /* end local hack... */ + if(but->type==BUT_TOGDUAL && data->togdual) { + if(but->pointype==SHO) + but->poin -= 2; + else if(but->pointype==INT) + but->poin -= 4; + } + + ui_apply_but_func(C, but); + + data->retval= but->retval; + data->applied= 1; +} + +static void ui_apply_but_ROW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data) +{ + ui_set_but_val(but, but->hardmax); + ui_apply_but_func(C, but); + + data->retval= but->retval; + data->applied= 1; +} + +static void ui_apply_but_TEX(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + if(!data->str) + return; + + ui_set_but_string(C, but, data->str); + ui_check_but(but); + + /* give butfunc the original text too */ + /* feature used for bone renaming, channels, etc */ + /* XXX goes via uiButHandleRenameFunc now */ +// if(but->func_arg2==NULL) but->func_arg2= data->origstr; + ui_apply_but_func(C, but); +// if(but->func_arg2==data->origstr) but->func_arg2= NULL; + + data->retval= but->retval; + data->applied= 1; +} + +static void ui_apply_but_NUM(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + if(data->str) { + if(ui_set_but_string(C, but, data->str)) { + data->value= ui_get_but_val(but); + } + else { + data->cancel= 1; + return; + } + } + else + ui_set_but_val(but, data->value); + + ui_check_but(but); + ui_apply_but_func(C, but); + + data->retval= but->retval; + data->applied= 1; +} + +static void ui_apply_but_TOG3(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + if(but->pointype==SHO ) { + short *sp= (short *)but->poin; + + if( BTST(sp[1], but->bitnr)) { + sp[1]= BCLR(sp[1], but->bitnr); + sp[0]= BCLR(sp[0], but->bitnr); + } + else if( BTST(sp[0], but->bitnr)) { + sp[1]= BSET(sp[1], but->bitnr); + } else { + sp[0]= BSET(sp[0], but->bitnr); + } + } + else { + if( BTST(*(but->poin+2), but->bitnr)) { + *(but->poin+2)= BCLR(*(but->poin+2), but->bitnr); + *(but->poin)= BCLR(*(but->poin), but->bitnr); + } + else if( BTST(*(but->poin), but->bitnr)) { + *(but->poin+2)= BSET(*(but->poin+2), but->bitnr); + } else { + *(but->poin)= BSET(*(but->poin), but->bitnr); + } + } + + ui_check_but(but); + ui_apply_but_func(C, but); + data->retval= but->retval; + data->applied= 1; +} + +static void ui_apply_but_VEC(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + ui_set_but_vectorf(but, data->vec); + ui_check_but(but); + ui_apply_but_func(C, but); + + data->retval= but->retval; + data->applied= 1; +} + +static void ui_apply_but_COLORBAND(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + ui_apply_but_func(C, but); + data->retval= but->retval; + data->applied= 1; +} + +static void ui_apply_but_CURVE(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + ui_apply_but_func(C, but); + data->retval= but->retval; + data->applied= 1; +} + +static void ui_apply_but_IDPOIN(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + ui_set_but_string(C, but, data->str); + ui_check_but(but); + ui_apply_but_func(C, but); + data->retval= but->retval; + data->applied= 1; +} + +#ifdef INTERNATIONAL +static void ui_apply_but_CHARTAB(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + ui_apply_but_func(C, but); + data->retval= but->retval; + data->applied= 1; +} +#endif + +static void ui_apply_button(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, int interactive) +{ + char *editstr; + double *editval; + float *editvec; + ColorBand *editcoba; + CurveMapping *editcumap; + + data->retval= 0; + + /* if we cancel and have not applied yet, there is nothing to do, + * otherwise we have to restore the original value again */ + if(data->cancel) { + if(!data->applied) + return; + + if(data->str) MEM_freeN(data->str); + data->str= data->origstr; + data->origstr= NULL; + data->value= data->origvalue; + data->origvalue= 0.0; + VECCOPY(data->vec, data->origvec); + data->origvec[0]= data->origvec[1]= data->origvec[2]= 0.0f; + } + else { + /* we avoid applying interactive edits a second time + * at the end with the appliedinteractive flag */ + if(interactive) + data->appliedinteractive= 1; + else if(data->appliedinteractive) + return; + } + + /* ensures we are writing actual values */ + editstr= but->editstr; + editval= but->editval; + editvec= but->editvec; + editcoba= but->editcoba; + editcumap= but->editcumap; + but->editstr= NULL; + but->editval= NULL; + but->editvec= NULL; + but->editcoba= NULL; + but->editcumap= NULL; + + /* handle different types */ + switch(but->type) { + case BUT: + ui_apply_but_BUT(C, but, data); + break; + case TEX: + case SEARCH_MENU: + ui_apply_but_TEX(C, but, data); + break; + case TOGBUT: + case TOG: + case TOGR: + case ICONTOG: + case ICONTOGN: + case TOGN: + case BUT_TOGDUAL: + case OPTION: + case OPTIONN: + ui_apply_but_TOG(C, block, but, data); + break; + case ROW: + ui_apply_but_ROW(C, block, but, data); + break; + case SCROLL: + break; + case NUM: + case NUMABS: + ui_apply_but_NUM(C, but, data); + break; + case SLI: + case NUMSLI: + ui_apply_but_NUM(C, but, data); + break; + case HSVSLI: + break; + case TOG3: + ui_apply_but_TOG3(C, but, data); + break; + case MENU: + case ICONROW: + case ICONTEXTROW: + case BLOCK: + case PULLDOWN: + case COL: + ui_apply_but_BLOCK(C, but, data); + break; + case BUTM: + ui_apply_but_BUTM(C, but, data); + break; + case BUT_NORMAL: + case HSVCUBE: + ui_apply_but_VEC(C, but, data); + break; + case BUT_COLORBAND: + ui_apply_but_COLORBAND(C, but, data); + break; + case BUT_CURVE: + ui_apply_but_CURVE(C, but, data); + break; + case IDPOIN: + ui_apply_but_IDPOIN(C, but, data); + break; +#ifdef INTERNATIONAL + case CHARTAB: + ui_apply_but_CHARTAB(C, but, data); + break; +#endif + case LINK: + case INLINK: + break; + default: + break; + } + + but->editstr= editstr; + but->editval= editval; + but->editvec= editvec; + but->editcoba= editcoba; + but->editcumap= editcumap; +} + +/* ******************* copy and paste ******************** */ + +/* c = copy, v = paste */ +static void ui_but_copy_paste(bContext *C, uiBut *but, uiHandleButtonData *data, char mode) +{ + static ColorBand but_copypaste_coba = {0}; + char buf[UI_MAX_DRAW_STR+1]= {0}; + double val; + + if(mode=='v' && but->lock) + return; + + if(mode=='v') { + /* extract first line from clipboard in case of multi-line copies */ + char *p, *pbuf= WM_clipboard_text_get(0); + p= pbuf; + if(p) { + int i = 0; + while (*p && *p!='\r' && *p!='\n' && i<UI_MAX_DRAW_STR) { + buf[i++]=*p; + p++; + } + buf[i]= 0; + MEM_freeN(pbuf); + } + } + + /* numeric value */ + if ELEM4(but->type, NUM, NUMABS, NUMSLI, HSVSLI) { + + if(but->poin==NULL && but->rnapoin.data==NULL); + else if(mode=='c') { + sprintf(buf, "%f", ui_get_but_val(but)); + WM_clipboard_text_set(buf, 0); + } + else { + if (sscanf(buf, " %lf ", &val) == 1) { + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + data->value= val; + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + } + + /* RGB triple */ + else if(but->type==COL) { + float rgb[3]; + + if(but->poin==NULL && but->rnapoin.data==NULL); + else if(mode=='c') { + + ui_get_but_vectorf(but, rgb); + sprintf(buf, "[%f, %f, %f]", rgb[0], rgb[1], rgb[2]); + WM_clipboard_text_set(buf, 0); + + } + else { + if (sscanf(buf, "[%f, %f, %f]", &rgb[0], &rgb[1], &rgb[2]) == 3) { + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + VECCOPY(data->vec, rgb); + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + } + + /* text/string and ID data */ + else if(ELEM(but->type, TEX, IDPOIN)) { + uiHandleButtonData *data= but->active; + + if(but->poin==NULL && but->rnapoin.data==NULL); + else if(mode=='c') { + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + BLI_strncpy(buf, data->str, UI_MAX_DRAW_STR); + WM_clipboard_text_set(data->str, 0); + data->cancel= 1; + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else { + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + BLI_strncpy(data->str, buf, data->maxlen); + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + /* colorband (not supported by system clipboard) */ + else if(but->type==BUT_COLORBAND) { + if(mode=='c') { + if(but->poin) + return; + + memcpy(&but_copypaste_coba, but->poin, sizeof(ColorBand)); + } + else { + if(but_copypaste_coba.tot==0) + return; + + if(!but->poin) + but->poin= MEM_callocN(sizeof(ColorBand), "colorband"); + + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + memcpy(data->coba, &but_copypaste_coba, sizeof(ColorBand) ); + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } +} + +/* ************* in-button text selection/editing ************* */ + +/* return 1 if char ch is special character, otherwise return 0 */ +static short test_special_char(char ch) +{ + switch(ch) { + case '\\': + case '/': + case '~': + case '!': + case '@': + case '#': + case '$': + case '%': + case '^': + case '&': + case '*': + case '(': + case ')': + case '+': + case '=': + case '{': + case '}': + case '[': + case ']': + case ':': + case ';': + case '\'': + case '\"': + case '<': + case '>': + case ',': + case '.': + case '?': + case '_': + case '-': + case ' ': + return 1; + break; + default: + break; + } + return 0; +} + +static int ui_textedit_delete_selection(uiBut *but, uiHandleButtonData *data) +{ + char *str; + int x, changed; + + str= data->str; + changed= (but->selsta != but->selend); + + for(x=0; x< strlen(str); x++) { + if (but->selend + x <= strlen(str) ) { + str[but->selsta + x]= str[but->selend + x]; + } else { + str[but->selsta + x]= '\0'; + break; + } + } + + but->pos = but->selend = but->selsta; + + return changed; +} + +static void ui_textedit_set_cursor_pos(uiBut *but, uiHandleButtonData *data, short x) +{ + uiStyle *style= U.uistyles.first; // XXX pass on as arg + int startx= but->x1; + char *origstr; + + uiStyleFontSet(&style->widget); + + origstr= MEM_callocN(sizeof(char)*(data->maxlen+1), "ui_textedit origstr"); + + BLI_strncpy(origstr, but->drawstr, data->maxlen+1); + but->pos= strlen(origstr)-but->ofs; + + /* XXX solve generic */ + if(but->type==NUM || but->type==NUMSLI) + startx += 20; + + while((BLF_width(origstr+but->ofs) + startx) > x) { + if (but->pos <= 0) break; + but->pos--; + origstr[but->pos+but->ofs] = 0; + } + + but->pos -= strlen(but->str); + but->pos += but->ofs; + if(but->pos<0) but->pos= 0; + + MEM_freeN(origstr); +} + +static void ui_textedit_set_cursor_select(uiBut *but, uiHandleButtonData *data, short x) +{ + if (x > data->selstartx) data->selextend = EXTEND_RIGHT; + else if (x < data->selstartx) data->selextend = EXTEND_LEFT; + + ui_textedit_set_cursor_pos(but, data, x); + + if (data->selextend == EXTEND_RIGHT) but->selend = but->pos; + if (data->selextend == EXTEND_LEFT) but->selsta = but->pos; + + ui_check_but(but); +} + +static int ui_textedit_type_ascii(uiBut *but, uiHandleButtonData *data, char ascii) +{ + char *str; + int len, x, changed= 0; + + str= data->str; + len= strlen(str); + + if(len-(but->selend - but->selsta)+1 <= data->maxlen) { + /* type over the current selection */ + if ((but->selend - but->selsta) > 0) + changed= ui_textedit_delete_selection(but, data); + + len= strlen(str); + if(len < data->maxlen) { + for(x= data->maxlen; x>but->pos; x--) + str[x]= str[x-1]; + str[but->pos]= ascii; + str[len+1]= '\0'; + + but->pos++; + changed= 1; + } + } + + return changed; +} + +void ui_textedit_move(uiBut *but, uiHandleButtonData *data, int direction, int select, int jump) +{ + char *str; + int len; + + str= data->str; + len= strlen(str); + + if(direction) { /* right*/ + /* if there's a selection */ + if ((but->selend - but->selsta) > 0) { + /* extend the selection based on the first direction taken */ + if(select) { + if (!data->selextend) { + data->selextend = EXTEND_RIGHT; + } + if (data->selextend == EXTEND_RIGHT) { + but->selend++; + if (but->selend > len) but->selend = len; + } else if (data->selextend == EXTEND_LEFT) { + but->selsta++; + /* if the selection start has gone past the end, + * flip them so they're in sync again */ + if (but->selsta == but->selend) { + but->pos = but->selsta; + data->selextend = EXTEND_RIGHT; + } + } + } else { + but->selsta = but->pos = but->selend; + data->selextend = 0; + } + } else { + if(select) { + /* make a selection, starting from the cursor position */ + but->selsta = but->pos; + + but->pos++; + if(but->pos>strlen(str)) but->pos= strlen(str); + + but->selend = but->pos; + } else if(jump) { + /* jump betweenn special characters (/,\,_,-, etc.), + * look at function test_special_char() for complete + * list of special character, ctr -> */ + while(but->pos < len) { + but->pos++; + if(test_special_char(str[but->pos])) break; + } + } else { + but->pos++; + if(but->pos>strlen(str)) but->pos= strlen(str); + } + } + } + else { /* left */ + /* if there's a selection */ + if ((but->selend - but->selsta) > 0) { + /* extend the selection based on the first direction taken */ + if(select) { + if (!data->selextend) { + data->selextend = EXTEND_LEFT; + } + if (data->selextend == EXTEND_LEFT) { + but->selsta--; + if (but->selsta < 0) but->selsta = 0; + } else if (data->selextend == EXTEND_RIGHT) { + but->selend--; + /* if the selection start has gone past the end, + * flip them so they're in sync again */ + if (but->selsta == but->selend) { + but->pos = but->selsta; + data->selextend = EXTEND_LEFT; + } + } + } else { + but->pos = but->selend = but->selsta; + data->selextend = 0; + } + } else { + if(select) { + /* make a selection, starting from the cursor position */ + but->selend = but->pos; + + but->pos--; + if(but->pos<0) but->pos= 0; + + but->selsta = but->pos; + } else if(jump) { + /* jump betweenn special characters (/,\,_,-, etc.), + * look at function test_special_char() for complete + * list of special character, ctr -> */ + while(but->pos > 0){ + but->pos--; + if(test_special_char(str[but->pos])) break; + } + } else { + if(but->pos>0) but->pos--; + } + } + } +} + +void ui_textedit_move_end(uiBut *but, uiHandleButtonData *data, int direction, int select) +{ + char *str; + + str= data->str; + + if(direction) { /* right */ + if(select) { + but->selsta = but->pos; + but->selend = strlen(str); + data->selextend = EXTEND_RIGHT; + } else { + but->selsta = but->selend = but->pos= strlen(str); + } + } + else { /* left */ + if(select) { + but->selend = but->pos; + but->selsta = 0; + data->selextend = EXTEND_LEFT; + } else { + but->selsta = but->selend = but->pos= 0; + } + } +} + +static int ui_textedit_delete(uiBut *but, uiHandleButtonData *data, int direction, int all) +{ + char *str; + int len, x, changed= 0; + + str= data->str; + len= strlen(str); + + if(all) { + if(len) changed=1; + str[0]= 0; + but->pos= 0; + } + else if(direction) { /* delete */ + if ((but->selend - but->selsta) > 0) { + changed= ui_textedit_delete_selection(but, data); + } + else if(but->pos>=0 && but->pos<len) { + for(x=but->pos; x<len; x++) + str[x]= str[x+1]; + str[len-1]='\0'; + changed= 1; + } + } + else { /* backspace */ + if(len!=0) { + if ((but->selend - but->selsta) > 0) { + changed= ui_textedit_delete_selection(but, data); + } + else if(but->pos>0) { + for(x=but->pos; x<len; x++) + str[x-1]= str[x]; + str[len-1]='\0'; + + but->pos--; + changed= 1; + } + } + } + + return changed; +} + +static int ui_textedit_autocomplete(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + char *str; + int changed= 1; + + str= data->str; + but->autocomplete_func(C, str, but->autofunc_arg); + but->pos= strlen(str); + + return changed; +} + +static int ui_textedit_copypaste(uiBut *but, uiHandleButtonData *data, int paste, int copy, int cut) +{ + char buf[UI_MAX_DRAW_STR]={0}; + char *str, *p, *pbuf; + int len, x, y, i, changed= 0; + + str= data->str; + len= strlen(str); + + /* paste */ + if (paste) { + /* extract the first line from the clipboard */ + p = pbuf= WM_clipboard_text_get(0); + + if(p && p[0]) { + i= 0; + while (*p && *p!='\r' && *p!='\n' && i<UI_MAX_DRAW_STR-1) { + buf[i++]=*p; + p++; + } + buf[i]= 0; + + /* paste over the current selection */ + if ((but->selend - but->selsta) > 0) + ui_textedit_delete_selection(but, data); + + for (y=0; y<strlen(buf); y++) + { + /* add contents of buffer */ + if(len < data->maxlen) { + for(x= data->maxlen; x>but->pos; x--) + str[x]= str[x-1]; + str[but->pos]= buf[y]; + but->pos++; + len++; + str[len]= '\0'; + } + } + + changed= 1; + } + + if(pbuf) + MEM_freeN(pbuf); + } + /* cut & copy */ + else if (copy || cut) { + /* copy the contents to the copypaste buffer */ + for(x= but->selsta; x <= but->selend; x++) { + if (x==but->selend) + buf[x] = '\0'; + else + buf[(x - but->selsta)] = str[x]; + } + + WM_clipboard_text_set(buf, 0); + + /* for cut only, delete the selection afterwards */ + if(cut) + if((but->selend - but->selsta) > 0) + changed= ui_textedit_delete_selection(but, data); + } + + return changed; +} + +static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + if(data->str) { + MEM_freeN(data->str); + data->str= NULL; + } + + /* retrieve string */ + data->maxlen= ui_get_but_string_max_length(but); + data->str= MEM_callocN(sizeof(char)*(data->maxlen+1), "textedit str"); + ui_get_but_string(but, data->str, data->maxlen+1); + + data->origstr= BLI_strdup(data->str); + data->selextend= 0; + data->selstartx= 0; + + /* set cursor pos to the end of the text */ + but->editstr= data->str; + but->pos= strlen(data->str); + but->selsta= 0; + but->selend= strlen(but->drawstr) - strlen(but->str); + + /* optional searchbox */ + if(but->type==SEARCH_MENU) { + data->searchbox= ui_searchbox_create(C, data->region, but); + ui_searchbox_update(C, data->searchbox, but, 1); /* 1= reset */ + } + + ui_check_but(but); +} + +static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + if(but) { + if(data->searchbox) { + if(data->cancel==0) + ui_searchbox_apply(but, data->searchbox); + + ui_searchbox_free(C, data->searchbox); + data->searchbox= NULL; + } + + but->editstr= NULL; + but->pos= -1; + } +} + +static void ui_textedit_next_but(uiBlock *block, uiBut *actbut, uiHandleButtonData *data) +{ + uiBut *but; + + /* label and roundbox can overlap real buttons (backdrops...) */ + if(actbut->type==LABEL && actbut->type==ROUNDBOX) + return; + + for(but= actbut->next; but; but= but->next) { + if(ELEM5(but->type, TEX, NUM, NUMABS, NUMSLI, HSVSLI)) { + data->postbut= but; + data->posttype= BUTTON_ACTIVATE_TEXT_EDITING; + return; + } + } + for(but= block->buttons.first; but!=actbut; but= but->next) { + if(ELEM5(but->type, TEX, NUM, NUMABS, NUMSLI, HSVSLI)) { + data->postbut= but; + data->posttype= BUTTON_ACTIVATE_TEXT_EDITING; + return; + } + } +} + +static void ui_textedit_prev_but(uiBlock *block, uiBut *actbut, uiHandleButtonData *data) +{ + uiBut *but; + + /* label and roundbox can overlap real buttons (backdrops...) */ + if(actbut->type==LABEL && actbut->type==ROUNDBOX) + return; + + for(but= actbut->prev; but; but= but->prev) { + if(ELEM5(but->type, TEX, NUM, NUMABS, NUMSLI, HSVSLI)) { + data->postbut= but; + data->posttype= BUTTON_ACTIVATE_TEXT_EDITING; + return; + } + } + for(but= block->buttons.last; but!=actbut; but= but->prev) { + if(ELEM5(but->type, TEX, NUM, NUMABS, NUMSLI, HSVSLI)) { + data->postbut= but; + data->posttype= BUTTON_ACTIVATE_TEXT_EDITING; + return; + } + } +} + + +static void ui_do_but_textedit(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, wmEvent *event) +{ + int mx, my, changed= 0, inbox=0, retval= WM_UI_HANDLER_CONTINUE; + + switch(event->type) { + case WHEELUPMOUSE: + case WHEELDOWNMOUSE: + case MOUSEMOVE: + if(data->searchbox) + ui_searchbox_event(C, data->searchbox, but, event); + + break; + case RIGHTMOUSE: + case ESCKEY: + data->cancel= 1; + data->escapecancel= 1; + button_activate_state(C, but, BUTTON_STATE_EXIT); + retval= WM_UI_HANDLER_BREAK; + break; + case LEFTMOUSE: { + + /* exit on LMB only on RELEASE for searchbox, to mimic other popups, and allow multiple menu levels */ + if(data->searchbox) + inbox= BLI_in_rcti(&data->searchbox->winrct, event->x, event->y); + + if(event->val==KM_PRESS) { + mx= event->x; + my= event->y; + ui_window_to_block(data->region, block, &mx, &my); + + if ((but->y1 <= my) && (my <= but->y2) && (but->x1 <= mx) && (mx <= but->x2)) { + ui_textedit_set_cursor_pos(but, data, mx); + but->selsta = but->selend = but->pos; + data->selstartx= mx; + + button_activate_state(C, but, BUTTON_STATE_TEXT_SELECTING); + retval= WM_UI_HANDLER_BREAK; + } + else if(inbox==0) { + /* if searchbox, click outside will cancel */ + if(data->searchbox) + data->cancel= data->escapecancel= 1; + button_activate_state(C, but, BUTTON_STATE_EXIT); + retval= WM_UI_HANDLER_BREAK; + } + } + else if(inbox) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + retval= WM_UI_HANDLER_BREAK; + } + break; + } + } + + if(event->val==KM_PRESS) { + switch (event->type) { + case VKEY: + case XKEY: + case CKEY: + if(event->ctrl || event->oskey) { + if(event->type == VKEY) + changed= ui_textedit_copypaste(but, data, 1, 0, 0); + else if(event->type == CKEY) + changed= ui_textedit_copypaste(but, data, 0, 1, 0); + else if(event->type == XKEY) + changed= ui_textedit_copypaste(but, data, 0, 0, 1); + + retval= WM_UI_HANDLER_BREAK; + } + break; + case RIGHTARROWKEY: + ui_textedit_move(but, data, 1, event->shift, event->ctrl); + retval= WM_UI_HANDLER_BREAK; + break; + case LEFTARROWKEY: + ui_textedit_move(but, data, 0, event->shift, event->ctrl); + retval= WM_UI_HANDLER_BREAK; + break; + case DOWNARROWKEY: + if(data->searchbox) { + ui_searchbox_event(C, data->searchbox, but, event); + break; + } + /* pass on purposedly */ + case ENDKEY: + ui_textedit_move_end(but, data, 1, event->shift); + retval= WM_UI_HANDLER_BREAK; + break; + case UPARROWKEY: + if(data->searchbox) { + ui_searchbox_event(C, data->searchbox, but, event); + break; + } + /* pass on purposedly */ + case HOMEKEY: + ui_textedit_move_end(but, data, 0, event->shift); + retval= WM_UI_HANDLER_BREAK; + break; + case PADENTER: + case RETKEY: + button_activate_state(C, but, BUTTON_STATE_EXIT); + retval= WM_UI_HANDLER_BREAK; + break; + case DELKEY: + changed= ui_textedit_delete(but, data, 1, 0); + retval= WM_UI_HANDLER_BREAK; + break; + + case BACKSPACEKEY: + changed= ui_textedit_delete(but, data, 0, event->shift); + retval= WM_UI_HANDLER_BREAK; + break; + + case TABKEY: + /* there is a key conflict here, we can't tab with autocomplete */ + if(but->autocomplete_func) { + changed= ui_textedit_autocomplete(C, but, data); + retval= WM_UI_HANDLER_BREAK; + } + /* the hotkey here is not well defined, was G.qual so we check all */ + else if(event->shift || event->ctrl || event->alt || event->oskey) { + ui_textedit_prev_but(block, but, data); + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else { + ui_textedit_next_but(block, but, data); + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + retval= WM_UI_HANDLER_BREAK; + break; + } + + if(event->ascii && (retval == WM_UI_HANDLER_CONTINUE)) { + changed= ui_textedit_type_ascii(but, data, event->ascii); + retval= WM_UI_HANDLER_BREAK; + } + } + + if(changed) { + if(data->interactive) ui_apply_button(C, block, but, data, 1); + else ui_check_but(but); + + if(data->searchbox) + ui_searchbox_update(C, data->searchbox, but, 1); /* 1 = reset */ + } + + if(changed || (retval == WM_UI_HANDLER_BREAK)) + ED_region_tag_redraw(data->region); +} + +static void ui_do_but_textedit_select(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, wmEvent *event) +{ + int mx, my, retval= WM_UI_HANDLER_CONTINUE; + + switch(event->type) { + case MOUSEMOVE: { + mx= event->x; + my= event->y; + ui_window_to_block(data->region, block, &mx, &my); + + ui_textedit_set_cursor_select(but, data, mx); + retval= WM_UI_HANDLER_BREAK; + break; + } + case LEFTMOUSE: + if(event->val == 0) + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + retval= WM_UI_HANDLER_BREAK; + break; + } + + if(retval == WM_UI_HANDLER_BREAK) { + ui_check_but(but); + ED_region_tag_redraw(data->region); + } +} + +/* ************* number editing for various types ************* */ + +static void ui_numedit_begin(uiBut *but, uiHandleButtonData *data) +{ + float softrange, softmin, softmax; + + if(but->type == BUT_CURVE) { + data->cumap= (CurveMapping*)but->poin; + but->editcumap= data->coba; + } + else if(but->type == BUT_COLORBAND) { + data->coba= (ColorBand*)but->poin; + but->editcoba= data->coba; + } + else if(ELEM(but->type, BUT_NORMAL, HSVCUBE)) { + ui_get_but_vectorf(but, data->origvec); + VECCOPY(data->vec, data->origvec); + but->editvec= data->vec; + } + else { + data->startvalue= ui_get_but_val(but); + data->origvalue= data->startvalue; + data->value= data->origvalue; + but->editval= &data->value; + + softmin= but->softmin; + softmax= but->softmax; + softrange= softmax - softmin; + + data->dragfstart= (softrange == 0.0)? 0.0: (data->value - softmin)/softrange; + data->dragf= data->dragfstart; + } + + data->dragchange= 0; + data->draglock= 1; +} + +static void ui_numedit_end(uiBut *but, uiHandleButtonData *data) +{ + but->editval= NULL; + but->editvec= NULL; + but->editcoba= NULL; + but->editcumap= NULL; + + data->dragstartx= 0; + data->draglastx= 0; + data->dragchange= 0; + data->dragcbd= NULL; + data->dragsel= 0; +} + +static void ui_numedit_apply(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data) +{ + if(data->interactive) ui_apply_button(C, block, but, data, 1); + else ui_check_but(but); + + ED_region_tag_redraw(data->region); +} + +/* ****************** menu opening for various types **************** */ + +static void ui_blockopen_begin(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + uiBlockCreateFunc func= NULL; + uiBlockHandleCreateFunc handlefunc= NULL; + uiMenuCreateFunc menufunc= NULL; + void *arg= NULL; + + switch(but->type) { + case BLOCK: + case PULLDOWN: + if(but->menu_create_func) { + menufunc= but->menu_create_func; + arg= but->poin; + } + else { + func= but->block_create_func; + arg= but->poin; + } + break; + case MENU: + if(but->menu_create_func) { + menufunc= but->menu_create_func; + arg= but->poin; + } + else { + data->origvalue= ui_get_but_val(but); + data->value= data->origvalue; + but->editval= &data->value; + + handlefunc= ui_block_func_MENU; + arg= but; + } + break; + case ICONROW: + handlefunc= ui_block_func_ICONROW; + arg= but; + break; + case ICONTEXTROW: + handlefunc= ui_block_func_ICONTEXTROW; + arg= but; + break; + case COL: + ui_get_but_vectorf(but, data->origvec); + VECCOPY(data->vec, data->origvec); + but->editvec= data->vec; + + handlefunc= ui_block_func_COL; + arg= but; + break; + } + + if(func || handlefunc) { + data->menu= ui_popup_block_create(C, data->region, but, func, handlefunc, arg); + if(but->block->handle) + data->menu->popup= but->block->handle->popup; + } + else if(menufunc) { + data->menu= ui_popup_menu_create(C, data->region, but, menufunc, arg); + if(but->block->handle) + data->menu->popup= but->block->handle->popup; + } + + /* this makes adjacent blocks auto open from now on */ + //if(but->block->auto_open==0) but->block->auto_open= 1; +} + +static void ui_blockopen_end(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + if(but) { + but->editval= NULL; + but->editvec= NULL; + + but->block->auto_open_last= PIL_check_seconds_timer(); + } + + if(data->menu) { + ui_popup_block_free(C, data->menu); + data->menu= NULL; + } +} + +/* ***************** events for different button types *************** */ + +static int ui_do_but_BUT(bContext *C, uiBut *but, uiHandleButtonData *data, wmEvent *event) +{ + if(data->state == BUTTON_STATE_HIGHLIGHT) { + if(event->type == LEFTMOUSE && event->val==KM_PRESS) { + button_activate_state(C, but, BUTTON_STATE_WAIT_RELEASE); + return WM_UI_HANDLER_BREAK; + } + else if(event->type == LEFTMOUSE && but->block->handle) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + return WM_UI_HANDLER_BREAK; + } + else if(ELEM(event->type, PADENTER, RETKEY) && event->val==KM_PRESS) { + button_activate_state(C, but, BUTTON_STATE_WAIT_FLASH); + return WM_UI_HANDLER_BREAK; + } + } + else if(data->state == BUTTON_STATE_WAIT_RELEASE) { + if(event->type == LEFTMOUSE && event->val!=KM_PRESS) { + if(!(but->flag & UI_SELECT)) + data->cancel= 1; + button_activate_state(C, but, BUTTON_STATE_EXIT); + return WM_UI_HANDLER_BREAK; + } + } + + return WM_UI_HANDLER_CONTINUE; +} + +static int ui_do_but_KEYEVT(bContext *C, uiBut *but, uiHandleButtonData *data, wmEvent *event) +{ + if(data->state == BUTTON_STATE_HIGHLIGHT) { + if(ELEM3(event->type, LEFTMOUSE, PADENTER, RETKEY) && event->val==KM_PRESS) { + button_activate_state(C, but, BUTTON_STATE_WAIT_KEY_EVENT); + return WM_UI_HANDLER_BREAK; + } + } + else if(data->state == BUTTON_STATE_WAIT_KEY_EVENT) { + if(event->type == MOUSEMOVE) + return WM_UI_HANDLER_CONTINUE; + + if(event->val==KM_PRESS) { + if(WM_key_event_string(event->type)[0]) + ui_set_but_val(but, event->type); + else + data->cancel= 1; + + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + + return WM_UI_HANDLER_CONTINUE; +} + +static int ui_do_but_TEX(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, wmEvent *event) +{ + if(data->state == BUTTON_STATE_HIGHLIGHT) { + if(ELEM4(event->type, LEFTMOUSE, PADENTER, RETKEY, EVT_BUT_OPEN) && event->val==KM_PRESS) { + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + return WM_UI_HANDLER_BREAK; + } + } + else if(data->state == BUTTON_STATE_TEXT_EDITING) { + ui_do_but_textedit(C, block, but, data, event); + return WM_UI_HANDLER_BREAK; + } + else if(data->state == BUTTON_STATE_TEXT_SELECTING) { + ui_do_but_textedit_select(C, block, but, data, event); + return WM_UI_HANDLER_BREAK; + } + + return WM_UI_HANDLER_CONTINUE; +} + +static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, wmEvent *event) +{ + if(data->state == BUTTON_STATE_HIGHLIGHT) { + if(ELEM3(event->type, LEFTMOUSE, PADENTER, RETKEY) && event->val==KM_PRESS) { + data->togdual= event->ctrl; + data->togonly= !event->shift; + button_activate_state(C, but, BUTTON_STATE_EXIT); + return WM_UI_HANDLER_BREAK; + } + } + return WM_UI_HANDLER_CONTINUE; +} + +static int ui_do_but_EXIT(bContext *C, uiBut *but, uiHandleButtonData *data, wmEvent *event) +{ + if(data->state == BUTTON_STATE_HIGHLIGHT) { + if(ELEM3(event->type, LEFTMOUSE, PADENTER, RETKEY) && event->val==KM_PRESS) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + return WM_UI_HANDLER_BREAK; + } + } + + return WM_UI_HANDLER_CONTINUE; +} + +static int ui_numedit_but_NUM(uiBut *but, uiHandleButtonData *data, float fac, int snap, int mx) +{ + float deler, tempf, softmin, softmax, softrange; + int lvalue, temp, changed= 0; + + if(mx == data->draglastx) + return changed; + + /* drag-lock - prevent unwanted scroll adjustments */ + /* change value (now 3) to adjust threshold in pixels */ + if(data->draglock) { + if(abs(mx-data->dragstartx) <= 3) + return changed; + + data->draglock= 0; + data->dragstartx= mx; /* ignore mouse movement within drag-lock */ + } + + softmin= but->softmin; + softmax= but->softmax; + softrange= softmax - softmin; + + deler= 500; + if(!ui_is_but_float(but)) { + if((softrange)<100) deler= 200.0; + if((softrange)<25) deler= 50.0; + } + deler /= fac; + + if(ui_is_but_float(but) && softrange > 11) { + /* non linear change in mouse input- good for high precicsion */ + data->dragf+= (((float)(mx-data->draglastx))/deler) * (fabs(data->dragstartx-mx)*0.002); + } else if (!ui_is_but_float(but) && softrange > 129) { /* only scale large int buttons */ + /* non linear change in mouse input- good for high precicsionm ints need less fine tuning */ + data->dragf+= (((float)(mx-data->draglastx))/deler) * (fabs(data->dragstartx-mx)*0.004); + } else { + /*no scaling */ + data->dragf+= ((float)(mx-data->draglastx))/deler ; + } + + if(data->dragf>1.0) data->dragf= 1.0; + if(data->dragf<0.0) data->dragf= 0.0; + data->draglastx= mx; + tempf= (softmin + data->dragf*softrange); + + if(!ui_is_but_float(but)) { + temp= floor(tempf+.5); + + if(tempf==softmin || tempf==softmax); + else if(snap) { + if(snap == 2) temp= 100*(temp/100); + else temp= 10*(temp/10); + } + + CLAMP(temp, softmin, softmax); + lvalue= (int)data->value; + + if(temp != lvalue) { + data->dragchange= 1; + data->value= (double)temp; + changed= 1; + } + } + else { + temp= 0; + + if(snap) { + if(snap == 2) { + if(tempf==softmin || tempf==softmax); + else if(softrange < 2.10) tempf= 0.01*floor(100.0*tempf); + else if(softrange < 21.0) tempf= 0.1*floor(10.0*tempf); + else tempf= floor(tempf); + } + else { + if(tempf==softmin || tempf==softmax); + else if(softrange < 2.10) tempf= 0.1*floor(10*tempf); + else if(softrange < 21.0) tempf= floor(tempf); + else tempf= 10.0*floor(tempf/10.0); + } + } + + CLAMP(tempf, softmin, softmax); + + if(tempf != data->value) { + data->dragchange= 1; + data->value= tempf; + changed= 1; + } + } + + return changed; +} + +static int ui_do_but_NUM(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, wmEvent *event) +{ + int mx, my, click= 0; + int retval= WM_UI_HANDLER_CONTINUE; + + mx= event->x; + my= event->y; + ui_window_to_block(data->region, block, &mx, &my); + + if(data->state == BUTTON_STATE_HIGHLIGHT) { + /* XXX hardcoded keymap check.... */ + if(event->type == WHEELDOWNMOUSE && event->alt) { + mx= but->x1; + click= 1; + } + else if(event->type == WHEELUPMOUSE && event->alt) { + mx= but->x2; + click= 1; + } + else if(event->val==KM_PRESS) { + if(ELEM3(event->type, LEFTMOUSE, PADENTER, RETKEY) && event->shift) { + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + retval= WM_UI_HANDLER_BREAK; + } + else if(event->type == LEFTMOUSE) { + data->dragstartx= mx; + data->draglastx= mx; + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + retval= WM_UI_HANDLER_BREAK; + } + else if(ELEM(event->type, PADENTER, RETKEY) && event->val==KM_PRESS) + click= 1; + } + + } + else if(data->state == BUTTON_STATE_NUM_EDITING) { + if(event->type == ESCKEY) { + data->cancel= 1; + data->escapecancel= 1; + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else if(event->type == LEFTMOUSE && event->val!=KM_PRESS) { + if(data->dragchange) + button_activate_state(C, but, BUTTON_STATE_EXIT); + else + click= 1; + } + else if(event->type == MOUSEMOVE) { + float fac; + int snap; + + fac= 1.0f; + if(event->shift) fac /= 10.0f; + if(event->alt) fac /= 20.0f; + + if(event->custom == EVT_DATA_TABLET) { + wmTabletData *wmtab= event->customdata; + + /* de-sensitise based on tablet pressure */ + if (ELEM(wmtab->Active, DEV_STYLUS, DEV_ERASER)) + fac *= wmtab->Pressure; + } + + snap= (event->ctrl)? (event->shift)? 2: 1: 0; + + if(ui_numedit_but_NUM(but, data, fac, snap, mx)) + ui_numedit_apply(C, block, but, data); + } + retval= WM_UI_HANDLER_BREAK; + } + else if(data->state == BUTTON_STATE_TEXT_EDITING) { + ui_do_but_textedit(C, block, but, data, event); + retval= WM_UI_HANDLER_BREAK; + } + else if(data->state == BUTTON_STATE_TEXT_SELECTING) { + ui_do_but_textedit_select(C, block, but, data, event); + retval= WM_UI_HANDLER_BREAK; + } + + if(click) { + /* we can click on the side arrows to increment/decrement, + * or click inside to edit the value directly */ + float tempf, softmin, softmax; + int temp; + + softmin= but->softmin; + softmax= but->softmax; + + if(!ui_is_but_float(but)) { + if(mx < (but->x1 + (but->x2 - but->x1)/3 - 3)) { + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + + temp= (int)data->value - 1; + if(temp>=softmin && temp<=softmax) + data->value= (double)temp; + else + data->cancel= 1; + + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else if(mx > (but->x1 + (2*(but->x2 - but->x1)/3) + 3)) { + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + + temp= (int)data->value + 1; + if(temp>=softmin && temp<=softmax) + data->value= (double)temp; + else + data->cancel= 1; + + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + } + else { + if(mx < (but->x1 + (but->x2 - but->x1)/3 - 3)) { + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + + tempf= data->value - 0.01*but->a1; + if (tempf < softmin) tempf = softmin; + data->value= tempf; + + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else if(mx > but->x1 + (2*((but->x2 - but->x1)/3) + 3)) { + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + + tempf= data->value + 0.01*but->a1; + if (tempf > softmax) tempf = softmax; + data->value= tempf; + + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + } + + retval= WM_UI_HANDLER_BREAK; + } + + return retval; +} + +static int ui_numedit_but_SLI(uiBut *but, uiHandleButtonData *data, int shift, int ctrl, int mx) +{ + float deler, f, tempf, softmin, softmax, softrange; + int temp, lvalue, changed= 0; + + softmin= but->softmin; + softmax= but->softmax; + softrange= softmax - softmin; + + if(but->type==NUMSLI) deler= ((but->x2-but->x1) - 5.0*but->aspect); + else if(but->type==HSVSLI) deler= ((but->x2-but->x1)/2 - 5.0*but->aspect); + else deler= (but->x2-but->x1- 5.0*but->aspect); + + f= (float)(mx-data->dragstartx)/deler + data->dragfstart; + + if(shift) + f= (f-data->dragfstart)/10.0 + data->dragfstart; + + CLAMP(f, 0.0, 1.0); + tempf= softmin + f*softrange; + temp= floor(tempf+.5); + + if(ctrl) { + if(tempf==softmin || tempf==softmax); + else if(ui_is_but_float(but)) { + + if(shift) { + if(tempf==softmin || tempf==softmax); + else if(softmax-softmin < 2.10) tempf= 0.01*floor(100.0*tempf); + else if(softmax-softmin < 21.0) tempf= 0.1*floor(10.0*tempf); + else tempf= floor(tempf); + } + else { + if(softmax-softmin < 2.10) tempf= 0.1*floor(10*tempf); + else if(softmax-softmin < 21.0) tempf= floor(tempf); + else tempf= 10.0*floor(tempf/10.0); + } + } + else { + temp= 10*(temp/10); + tempf= temp; + } + } + + if(!ui_is_but_float(but)) { + lvalue= floor(data->value+0.5); + + CLAMP(temp, softmin, softmax); + + if(temp != lvalue) { + data->value= temp; + data->dragchange= 1; + changed= 1; + } + } + else { + CLAMP(tempf, softmin, softmax); + + if(tempf != data->value) { + data->value= tempf; + data->dragchange= 1; + changed= 1; + } + } + + return changed; +} + +static int ui_do_but_SLI(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, wmEvent *event) +{ + int mx, my, click= 0; + int retval= WM_UI_HANDLER_CONTINUE; + + mx= event->x; + my= event->y; + ui_window_to_block(data->region, block, &mx, &my); + + if(data->state == BUTTON_STATE_HIGHLIGHT) { + /* XXX hardcoded keymap check.... */ + if(event->type == WHEELDOWNMOUSE && event->alt) { + mx= but->x1; + click= 2; + } + else if(event->type == WHEELUPMOUSE && event->alt) { + mx= but->x2; + click= 2; + } + else if(event->val==KM_PRESS) { + if(ELEM3(event->type, LEFTMOUSE, PADENTER, RETKEY) && event->shift) { + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + retval= WM_UI_HANDLER_BREAK; + } + else if(event->type == LEFTMOUSE) { + data->dragstartx= mx; + data->draglastx= mx; + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + retval= WM_UI_HANDLER_BREAK; + } + else if(ELEM(event->type, PADENTER, RETKEY) && event->val==KM_PRESS) + click= 1; + } + } + else if(data->state == BUTTON_STATE_NUM_EDITING) { + if(event->type == ESCKEY) { + data->cancel= 1; + data->escapecancel= 1; + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else if(event->type == LEFTMOUSE && event->val!=KM_PRESS) { + if(data->dragchange) + button_activate_state(C, but, BUTTON_STATE_EXIT); + else + click= 1; + } + else if(event->type == MOUSEMOVE) { + if(ui_numedit_but_SLI(but, data, event->shift, event->ctrl, mx)) + ui_numedit_apply(C, block, but, data); + } + retval= WM_UI_HANDLER_BREAK; + } + else if(data->state == BUTTON_STATE_TEXT_EDITING) { + ui_do_but_textedit(C, block, but, data, event); + retval= WM_UI_HANDLER_BREAK; + } + else if(data->state == BUTTON_STATE_TEXT_SELECTING) { + ui_do_but_textedit_select(C, block, but, data, event); + retval= WM_UI_HANDLER_BREAK; + } + + if(click) { + if (event->ctrl || click==2) { + /* nudge slider to the left or right */ + float f, tempf, softmin, softmax, softrange; + int temp; + + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + + softmin= but->softmin; + softmax= but->softmax; + softrange= softmax - softmin; + + tempf= data->value; + temp= (int)data->value; + + if(but->type==SLI) f= (float)(mx-but->x1)/(but->x2-but->x1); + else f= (float)(mx- but->x1)/(but->x2-but->x1); + + f= softmin + f*softrange; + + if(!ui_is_but_float(but)) { + if(f<temp) temp--; + else temp++; + + if(temp>=softmin && temp<=softmax) + data->value= temp; + else + data->cancel= 1; + } + else { + if(f<tempf) tempf-=.01; + else tempf+=.01; + + if(tempf>=softmin && tempf<=softmax) + data->value= tempf; + else + data->cancel= 1; + } + + button_activate_state(C, but, BUTTON_STATE_EXIT); + retval= WM_UI_HANDLER_BREAK; + } + else { + /* edit the value directly */ + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + retval= WM_UI_HANDLER_BREAK; + } + } + + return retval; +} + +static int ui_do_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data, wmEvent *event) +{ + if(data->state == BUTTON_STATE_HIGHLIGHT) { + if(ELEM3(event->type, LEFTMOUSE, PADENTER, RETKEY) && event->val==KM_PRESS) { + button_activate_state(C, but, BUTTON_STATE_MENU_OPEN); + return WM_UI_HANDLER_BREAK; + } + else if(ELEM3(but->type, MENU, ICONROW, ICONTEXTROW)) { + + if(event->type == WHEELDOWNMOUSE && event->alt) { + data->value= ui_step_name_menu(but, -1); + button_activate_state(C, but, BUTTON_STATE_EXIT); + ui_apply_button(C, but->block, but, data, 1); + return WM_UI_HANDLER_BREAK; + } + else if(event->type == WHEELUPMOUSE && event->alt) { + data->value= ui_step_name_menu(but, 1); + button_activate_state(C, but, BUTTON_STATE_EXIT); + ui_apply_button(C, but->block, but, data, 1); + return WM_UI_HANDLER_BREAK; + } + } + } + + return WM_UI_HANDLER_CONTINUE; +} + +static int ui_numedit_but_NORMAL(uiBut *but, uiHandleButtonData *data, int mx, int my) +{ + float dx, dy, rad, radsq, mrad, *fp; + int mdx, mdy, changed= 1; + + /* button is presumed square */ + /* if mouse moves outside of sphere, it does negative normal */ + + fp= data->origvec; + rad= (but->x2 - but->x1); + radsq= rad*rad; + + if(fp[2]>0.0f) { + mdx= (rad*fp[0]); + mdy= (rad*fp[1]); + } + else if(fp[2]> -1.0f) { + mrad= rad/sqrt(fp[0]*fp[0] + fp[1]*fp[1]); + + mdx= 2.0f*mrad*fp[0] - (rad*fp[0]); + mdy= 2.0f*mrad*fp[1] - (rad*fp[1]); + } + else mdx= mdy= 0; + + dx= (float)(mx+mdx-data->dragstartx); + dy= (float)(my+mdy-data->dragstarty); + + fp= data->vec; + mrad= dx*dx+dy*dy; + if(mrad < radsq) { /* inner circle */ + fp[0]= dx; + fp[1]= dy; + fp[2]= sqrt( radsq-dx*dx-dy*dy ); + } + else { /* outer circle */ + + mrad= rad/sqrt(mrad); // veclen + + dx*= (2.0f*mrad - 1.0f); + dy*= (2.0f*mrad - 1.0f); + + mrad= dx*dx+dy*dy; + if(mrad < radsq) { + fp[0]= dx; + fp[1]= dy; + fp[2]= -sqrt( radsq-dx*dx-dy*dy ); + } + } + Normalize(fp); + + data->draglastx= mx; + data->draglasty= my; + + return changed; +} + +static int ui_do_but_NORMAL(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, wmEvent *event) +{ + int mx, my; + + mx= event->x; + my= event->y; + ui_window_to_block(data->region, block, &mx, &my); + + if(data->state == BUTTON_STATE_HIGHLIGHT) { + if(event->type==LEFTMOUSE && event->val==KM_PRESS) { + data->dragstartx= mx; + data->dragstarty= my; + data->draglastx= mx; + data->draglasty= my; + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + + /* also do drag the first time */ + if(ui_numedit_but_NORMAL(but, data, mx, my)) + ui_numedit_apply(C, block, but, data); + + return WM_UI_HANDLER_BREAK; + } + } + else if(data->state == BUTTON_STATE_NUM_EDITING) { + if(event->type == MOUSEMOVE) { + if(mx!=data->draglastx || my!=data->draglasty) { + if(ui_numedit_but_NORMAL(but, data, mx, my)) + ui_numedit_apply(C, block, but, data); + } + } + else if(event->type==LEFTMOUSE && event->val!=KM_PRESS) + button_activate_state(C, but, BUTTON_STATE_EXIT); + + return WM_UI_HANDLER_BREAK; + } + + return WM_UI_HANDLER_CONTINUE; +} + +static int ui_numedit_but_HSVCUBE(uiBut *but, uiHandleButtonData *data, int mx, int my) +{ + float x, y; + int changed= 1; + + /* relative position within box */ + x= ((float)mx-but->x1)/(but->x2-but->x1); + y= ((float)my-but->y1)/(but->y2-but->y1); + CLAMP(x, 0.0, 1.0); + CLAMP(y, 0.0, 1.0); + + if(but->a1==0) { + but->hsv[0]= x; + but->hsv[2]= y; + } + else if(but->a1==1) { + but->hsv[0]= x; + but->hsv[1]= y; + } + else if(but->a1==2) { + but->hsv[2]= x; + but->hsv[1]= y; + } + else + but->hsv[0]= x; + + ui_set_but_hsv(but); // converts to rgb + + // update button values and strings + ui_update_block_buts_hsv(but->block, but->hsv); + + data->draglastx= mx; + data->draglasty= my; + + return changed; +} + +static int ui_do_but_HSVCUBE(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, wmEvent *event) +{ + int mx, my; + + mx= event->x; + my= event->y; + ui_window_to_block(data->region, block, &mx, &my); + + if(data->state == BUTTON_STATE_HIGHLIGHT) { + if(event->type==LEFTMOUSE && event->val==KM_PRESS) { + data->dragstartx= mx; + data->dragstarty= my; + data->draglastx= mx; + data->draglasty= my; + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + + /* also do drag the first time */ + if(ui_numedit_but_HSVCUBE(but, data, mx, my)) + ui_numedit_apply(C, block, but, data); + + return WM_UI_HANDLER_BREAK; + } + } + else if(data->state == BUTTON_STATE_NUM_EDITING) { + if(event->type == MOUSEMOVE) { + if(mx!=data->draglastx || my!=data->draglasty) { + if(ui_numedit_but_HSVCUBE(but, data, mx, my)) + ui_numedit_apply(C, block, but, data); + } + } + else if(event->type==LEFTMOUSE && event->val!=KM_PRESS) + button_activate_state(C, but, BUTTON_STATE_EXIT); + + return WM_UI_HANDLER_BREAK; + } + + return WM_UI_HANDLER_CONTINUE; +} + +static int verg_colorband(const void *a1, const void *a2) +{ + const CBData *x1=a1, *x2=a2; + + if( x1->pos > x2->pos ) return 1; + else if( x1->pos < x2->pos) return -1; + return WM_UI_HANDLER_CONTINUE; +} + +static void ui_colorband_update(ColorBand *coba) +{ + int a; + + if(coba->tot<2) return; + + for(a=0; a<coba->tot; a++) coba->data[a].cur= a; + qsort(coba->data, coba->tot, sizeof(CBData), verg_colorband); + for(a=0; a<coba->tot; a++) { + if(coba->data[a].cur==coba->cur) { + coba->cur= a; + break; + } + } +} + +static int ui_numedit_but_COLORBAND(uiBut *but, uiHandleButtonData *data, int mx) +{ + float dx; + int changed= 0; + + if(data->draglastx == mx) + return changed; + + dx= ((float)(mx - data->draglastx))/(but->x2-but->x1); + data->dragcbd->pos += dx; + CLAMP(data->dragcbd->pos, 0.0, 1.0); + + ui_colorband_update(data->coba); + data->dragcbd= data->coba->data + data->coba->cur; /* because qsort */ + + data->draglastx= mx; + changed= 1; + + return changed; +} + +static int ui_do_but_COLORBAND(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, wmEvent *event) +{ + ColorBand *coba; + CBData *cbd; + int mx, my, a, xco, mindist= 12; + + mx= event->x; + my= event->y; + ui_window_to_block(data->region, block, &mx, &my); + + if(data->state == BUTTON_STATE_HIGHLIGHT) { + if(event->type==LEFTMOUSE && event->val==KM_PRESS) { + coba= (ColorBand*)but->poin; + + if(event->ctrl) { + /* insert new key on mouse location */ + if(coba->tot < MAXCOLORBAND-1) { + float pos= ((float)(mx - but->x1))/(but->x2-but->x1); + float col[4]; + + do_colorband(coba, pos, col); /* executes it */ + + coba->tot++; + coba->cur= coba->tot-1; + + coba->data[coba->cur].r= col[0]; + coba->data[coba->cur].g= col[1]; + coba->data[coba->cur].b= col[2]; + coba->data[coba->cur].a= col[3]; + coba->data[coba->cur].pos= pos; + + ui_colorband_update(coba); + } + + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else { + data->dragstartx= mx; + data->dragstarty= my; + data->draglastx= mx; + data->draglasty= my; + + /* activate new key when mouse is close */ + for(a=0, cbd= coba->data; a<coba->tot; a++, cbd++) { + xco= but->x1 + (cbd->pos*(but->x2-but->x1)); + xco= ABS(xco-mx); + if(a==coba->cur) xco+= 5; // selected one disadvantage + if(xco<mindist) { + coba->cur= a; + mindist= xco; + } + } + + data->dragcbd= coba->data + coba->cur; + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + } + + return WM_UI_HANDLER_BREAK; + } + } + else if(data->state == BUTTON_STATE_NUM_EDITING) { + if(event->type == MOUSEMOVE) { + if(mx!=data->draglastx || my!=data->draglasty) { + if(ui_numedit_but_COLORBAND(but, data, mx)) + ui_numedit_apply(C, block, but, data); + } + } + else if(event->type==LEFTMOUSE && event->val!=KM_PRESS) + button_activate_state(C, but, BUTTON_STATE_EXIT); + + return WM_UI_HANDLER_BREAK; + } + + return WM_UI_HANDLER_CONTINUE; +} + +static int ui_numedit_but_CURVE(uiBut *but, uiHandleButtonData *data, int snap, int mx, int my) +{ + CurveMapping *cumap= data->cumap; + CurveMap *cuma= cumap->cm+cumap->cur; + CurveMapPoint *cmp= cuma->curve; + float fx, fy, zoomx, zoomy, offsx, offsy; + int a, changed= 0; + + zoomx= (but->x2-but->x1)/(cumap->curr.xmax-cumap->curr.xmin); + zoomy= (but->y2-but->y1)/(cumap->curr.ymax-cumap->curr.ymin); + offsx= cumap->curr.xmin; + offsy= cumap->curr.ymin; + + if(data->dragsel != -1) { + int moved_point= 0; /* for ctrl grid, can't use orig coords because of sorting */ + + fx= (mx-data->draglastx)/zoomx; + fy= (my-data->draglasty)/zoomy; + for(a=0; a<cuma->totpoint; a++) { + if(cmp[a].flag & SELECT) { + float origx= cmp[a].x, origy= cmp[a].y; + cmp[a].x+= fx; + cmp[a].y+= fy; + if(snap) { + cmp[a].x= 0.125f*floor(0.5f + 8.0f*cmp[a].x); + cmp[a].y= 0.125f*floor(0.5f + 8.0f*cmp[a].y); + } + if(cmp[a].x!=origx || cmp[a].y!=origy) + moved_point= 1; + } + } + + curvemapping_changed(cumap, 0); /* no remove doubles */ + + if(moved_point) { + data->draglastx= mx; + data->draglasty= my; + changed= 1; + } + + data->dragchange= 1; /* mark for selection */ + } + else { + fx= (mx-data->draglastx)/zoomx; + fy= (my-data->draglasty)/zoomy; + + /* clamp for clip */ + if(cumap->flag & CUMA_DO_CLIP) { + if(cumap->curr.xmin-fx < cumap->clipr.xmin) + fx= cumap->curr.xmin - cumap->clipr.xmin; + else if(cumap->curr.xmax-fx > cumap->clipr.xmax) + fx= cumap->curr.xmax - cumap->clipr.xmax; + if(cumap->curr.ymin-fy < cumap->clipr.ymin) + fy= cumap->curr.ymin - cumap->clipr.ymin; + else if(cumap->curr.ymax-fy > cumap->clipr.ymax) + fy= cumap->curr.ymax - cumap->clipr.ymax; + } + + cumap->curr.xmin-=fx; + cumap->curr.ymin-=fy; + cumap->curr.xmax-=fx; + cumap->curr.ymax-=fy; + + data->draglastx= mx; + data->draglasty= my; + + changed= 1; + } + + return changed; +} + +static int ui_do_but_CURVE(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, wmEvent *event) +{ + int mx, my, a, changed= 0; + + mx= event->x; + my= event->y; + ui_window_to_block(data->region, block, &mx, &my); + + if(data->state == BUTTON_STATE_HIGHLIGHT) { + if(event->type==LEFTMOUSE && event->val==KM_PRESS) { + CurveMapping *cumap= (CurveMapping*)but->poin; + CurveMap *cuma= cumap->cm+cumap->cur; + CurveMapPoint *cmp= cuma->curve; + float fx, fy, zoomx, zoomy, offsx, offsy; + float dist, mindist= 200.0f; // 14 pixels radius + int sel= -1; + + zoomx= (but->x2-but->x1)/(cumap->curr.xmax-cumap->curr.xmin); + zoomy= (but->y2-but->y1)/(cumap->curr.ymax-cumap->curr.ymin); + offsx= cumap->curr.xmin; + offsy= cumap->curr.ymin; + + if(event->ctrl) { + fx= ((float)my - but->x1)/zoomx + offsx; + fy= ((float)my - but->y1)/zoomy + offsy; + + curvemap_insert(cuma, fx, fy); + curvemapping_changed(cumap, 0); + changed= 1; + } + + /* check for selecting of a point */ + cmp= cuma->curve; /* ctrl adds point, new malloc */ + for(a=0; a<cuma->totpoint; a++) { + fx= but->x1 + zoomx*(cmp[a].x-offsx); + fy= but->y1 + zoomy*(cmp[a].y-offsy); + dist= (fx-mx)*(fx-mx) + (fy-my)*(fy-my); + if(dist < mindist) { + sel= a; + mindist= dist; + } + } + + if (sel == -1) { + /* if the click didn't select anything, check if it's clicked on the + * curve itself, and if so, add a point */ + fx= ((float)mx - but->x1)/zoomx + offsx; + fy= ((float)my - but->y1)/zoomy + offsy; + + cmp= cuma->table; + + /* loop through the curve segment table and find what's near the mouse. + * 0.05 is kinda arbitrary, but seems to be what works nicely. */ + for(a=0; a<=CM_TABLE; a++) { + if ( ( fabs(fx - cmp[a].x) < (0.05) ) && ( fabs(fy - cmp[a].y) < (0.05) ) ) { + + curvemap_insert(cuma, fx, fy); + curvemapping_changed(cumap, 0); + + changed= 1; + + /* reset cmp back to the curve points again, rather than drawing segments */ + cmp= cuma->curve; + + /* find newly added point and make it 'sel' */ + for(a=0; a<cuma->totpoint; a++) + if(cmp[a].x == fx) + sel = a; + + break; + } + } + } + + if(sel!= -1) { + /* ok, we move a point */ + /* deselect all if this one is deselect. except if we hold shift */ + if(event->shift==0 && (cmp[sel].flag & SELECT)==0) + for(a=0; a<cuma->totpoint; a++) + cmp[a].flag &= ~SELECT; + cmp[sel].flag |= SELECT; + } + else { + /* move the view */ + data->cancel= 1; + } + + data->dragsel= sel; + + data->dragstartx= mx; + data->dragstarty= my; + data->draglastx= mx; + data->draglasty= my; + + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + return WM_UI_HANDLER_BREAK; + } + } + else if(data->state == BUTTON_STATE_NUM_EDITING) { + if(event->type == MOUSEMOVE) { + if(mx!=data->draglastx || my!=data->draglasty) { + if(ui_numedit_but_CURVE(but, data, event->shift, mx, my)) + ui_numedit_apply(C, block, but, data); + } + } + else if(event->type==LEFTMOUSE && event->val!=KM_PRESS) { + if(data->dragsel != -1) { + CurveMapping *cumap= data->cumap; + CurveMap *cuma= cumap->cm+cumap->cur; + CurveMapPoint *cmp= cuma->curve; + + if(!data->dragchange) { + /* deselect all, select one */ + if(event->shift==0) { + for(a=0; a<cuma->totpoint; a++) + cmp[a].flag &= ~SELECT; + cmp[data->dragsel].flag |= SELECT; + } + } + else + curvemapping_changed(cumap, 1); /* remove doubles */ + } + + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + + return WM_UI_HANDLER_BREAK; + } + + return WM_UI_HANDLER_CONTINUE; +} + +#ifdef INTERNATIONAL +static int ui_do_but_CHARTAB(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, wmEvent *event) +{ + /* XXX 2.50 bad global and state access */ +#if 0 + float sx, sy, ex, ey; + float width, height; + float butw, buth; + int mx, my, x, y, cs, che; + + mx= event->x; + my= event->y; + ui_window_to_block(data->region, block, &mx, &my); + + if(data->state == BUTTON_STATE_HIGHLIGHT) { + if(ELEM3(event->type, LEFTMOUSE, PADENTER, RETKEY) && event->val==KM_PRESS) { + /* Calculate the size of the button */ + width = abs(but->x2 - but->x1); + height = abs(but->y2 - but->y1); + + butw = floor(width / 12); + buth = floor(height / 6); + + /* Initialize variables */ + sx = but->x1; + ex = but->x1 + butw; + sy = but->y1 + height - buth; + ey = but->y1 + height; + + cs = G.charstart; + + /* And the character is */ + x = (int) ((mx / butw) - 0.5); + y = (int) (6 - ((my / buth) - 0.5)); + + che = cs + (y*12) + x; + + if(che > G.charmax) + che = 0; + + if(G.obedit) + { + do_textedit(0,0,che); + } + + button_activate_state(C, but, BUTTON_STATE_EXIT); + return WM_UI_HANDLER_BREAK; + } + else if(ELEM(event->type, WHEELUPMOUSE, PAGEUPKEY)) { + for(but= block->buttons.first; but; but= but->next) { + if(but->type == CHARTAB) { + G.charstart = G.charstart - (12*6); + if(G.charstart < 0) + G.charstart = 0; + if(G.charstart < G.charmin) + G.charstart = G.charmin; + ui_draw_but(but); + + //Really nasty... to update the num button from the same butblock + for(bt= block->buttons.first; bt; bt= bt->next) + { + if(ELEM(bt->type, NUM, NUMABS)) { + ui_check_but(bt); + ui_draw_but(bt); + } + } + retval=UI_CONT; + break; + } + } + + return WM_UI_HANDLER_BREAK; + } + else if(ELEM(event->type, WHEELDOWNMOUSE, PAGEDOWNKEY)) { + for(but= block->buttons.first; but; but= but->next) + { + if(but->type == CHARTAB) + { + G.charstart = G.charstart + (12*6); + if(G.charstart > (0xffff - 12*6)) + G.charstart = 0xffff - (12*6); + if(G.charstart > G.charmax - 12*6) + G.charstart = G.charmax - 12*6; + ui_draw_but(but); + + for(bt= block->buttons.first; bt; bt= bt->next) + { + if(ELEM(bt->type, NUM, NUMABS)) { + ui_check_but(bt); + ui_draw_but(bt); + } + } + + but->flag |= UI_ACTIVE; + retval=UI_RETURN_OK; + break; + } + } + + return WM_UI_HANDLER_BREAK; + } + } +#endif + + return WM_UI_HANDLER_CONTINUE; +} +#endif + +static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, wmEvent *event) +{ + uiHandleButtonData *data; + int retval; + + data= but->active; + retval= WM_UI_HANDLER_CONTINUE; + + if(but->flag & UI_BUT_DISABLED) + return WM_UI_HANDLER_BREAK; + + if(data->state == BUTTON_STATE_HIGHLIGHT) { + /* handle copy-paste */ + if(ELEM(event->type, CKEY, VKEY) && event->val==KM_PRESS && (event->ctrl || event->oskey)) { + ui_but_copy_paste(C, but, data, (event->type == CKEY)? 'c': 'v'); + return WM_UI_HANDLER_BREAK; + } + /* handle keyframeing */ + else if(event->type == IKEY && event->val == KM_PRESS) { + if(event->alt) + ui_but_anim_delete_keyframe(C); + else + ui_but_anim_insert_keyframe(C); + + ED_region_tag_redraw(CTX_wm_region(C)); + + return WM_UI_HANDLER_BREAK; + } + /* handle driver adding */ + else if(event->type == DKEY && event->val == KM_PRESS) { + if(event->alt) + ui_but_anim_remove_driver(C); + else + ui_but_anim_add_driver(C); + + ED_region_tag_redraw(CTX_wm_region(C)); + + return WM_UI_HANDLER_BREAK; + } + /* handle menu */ + else if(event->type == RIGHTMOUSE && event->val == KM_PRESS) { + ui_but_anim_menu(C, but); + return WM_UI_HANDLER_BREAK; + } + } + + /* verify if we can edit this button */ + if(ELEM(event->type, LEFTMOUSE, RETKEY)) { + /* this should become disabled button .. */ + if(but->lock) { + if(but->lockstr) { + BKE_report(NULL, RPT_WARNING, but->lockstr); + button_activate_state(C, but, BUTTON_STATE_EXIT); + return WM_UI_HANDLER_BREAK; + } + } + else if(but->pointype && but->poin==0) { + /* there's a pointer needed */ + BKE_reportf(NULL, RPT_WARNING, "DoButton pointer error: %s", but->str); + button_activate_state(C, but, BUTTON_STATE_EXIT); + return WM_UI_HANDLER_BREAK; + } + } + + switch(but->type) { + case BUT: + retval= ui_do_but_BUT(C, but, data, event); + break; + case KEYEVT: + retval= ui_do_but_KEYEVT(C, but, data, event); + break; + case TOGBUT: + case TOG: + case TOGR: + case ICONTOG: + case ICONTOGN: + case TOGN: + case BUT_TOGDUAL: + case OPTION: + case OPTIONN: + retval= ui_do_but_TOG(C, but, data, event); + break; +#if 0 + case SCROLL: + /* DrawBut(b, 1); */ + /* do_scrollbut(b); */ + /* DrawBut(b,0); */ + break; +#endif + case NUM: + case NUMABS: + retval= ui_do_but_NUM(C, block, but, data, event); + break; + case SLI: + case NUMSLI: + case HSVSLI: + retval= ui_do_but_SLI(C, block, but, data, event); + break; + case ROUNDBOX: + case LABEL: + case TOG3: + case ROW: + retval= ui_do_but_EXIT(C, but, data, event); + break; + case TEX: + case IDPOIN: + case SEARCH_MENU: + retval= ui_do_but_TEX(C, block, but, data, event); + break; + case MENU: + case ICONROW: + case ICONTEXTROW: + case BLOCK: + case PULLDOWN: + retval= ui_do_but_BLOCK(C, but, data, event); + break; + case BUTM: + retval= ui_do_but_BUT(C, but, data, event); + break; + case COL: + if(but->a1 == -1) // signal to prevent calling up color picker + retval= ui_do_but_EXIT(C, but, data, event); + else + retval= ui_do_but_BLOCK(C, but, data, event); + break; + case BUT_NORMAL: + retval= ui_do_but_NORMAL(C, block, but, data, event); + break; + case BUT_COLORBAND: + retval= ui_do_but_COLORBAND(C, block, but, data, event); + break; + case BUT_CURVE: + retval= ui_do_but_CURVE(C, block, but, data, event); + break; + case HSVCUBE: + retval= ui_do_but_HSVCUBE(C, block, but, data, event); + break; +#ifdef INTERNATIONAL + case CHARTAB: + retval= ui_do_but_CHARTAB(C, block, but, data, event); + break; +#endif + /* XXX 2.50 links not implemented yet */ +#if 0 + case LINK: + case INLINK: + retval= retval= ui_do_but_LINK(block, but); + break; +#endif + } + + return retval; +} + +/* ************************ button utilities *********************** */ + +static int ui_but_contains_pt(uiBut *but, int mx, int my) +{ + return ((but->x1<mx && but->x2>=mx) && (but->y1<my && but->y2>=my)); +} + +static uiBut *ui_but_find_activated(ARegion *ar) +{ + uiBlock *block; + uiBut *but; + + for(block=ar->uiblocks.first; block; block=block->next) + for(but=block->buttons.first; but; but= but->next) + if(but->active) + return but; + + return NULL; +} + +int ui_button_is_active(ARegion *ar) +{ + return (ui_but_find_activated(ar) != NULL); +} + +static void ui_blocks_set_tooltips(ARegion *ar, int enable) +{ + uiBlock *block; + + if(!ar) + return; + + /* we disabled buttons when when they were already shown, and + * re-enable them on mouse move */ + for(block=ar->uiblocks.first; block; block=block->next) + block->tooltipdisabled= !enable; +} + +static int ui_mouse_inside_region(ARegion *ar, int x, int y) +{ + uiBlock *block; + int mx, my; + + /* check if the mouse is in the region, and in case of a view2d also check + * if it is inside the view2d itself, not over scrollbars for example */ + if(!BLI_in_rcti(&ar->winrct, x, y)) { + for(block=ar->uiblocks.first; block; block=block->next) + block->auto_open= 0; + + return 0; + } + + if(ar->v2d.mask.xmin!=ar->v2d.mask.xmax) { + mx= x; + my= y; + ui_window_to_region(ar, &mx, &my); + + if(!BLI_in_rcti(&ar->v2d.mask, mx, my)) + return 0; + } + + return 1; +} + +static int ui_mouse_inside_button(ARegion *ar, uiBut *but, int x, int y) +{ + if(!ui_mouse_inside_region(ar, x, y)) + return 0; + + ui_window_to_block(ar, but->block, &x, &y); + + if(!ui_but_contains_pt(but, x, y)) + return 0; + + return 1; +} + +static uiBut *ui_but_find_mouse_over(ARegion *ar, int x, int y) +{ + uiBlock *block; + uiBut *but, *butover= NULL; + int mx, my; + + if(!ui_mouse_inside_region(ar, x, y)) + return NULL; + + for(block=ar->uiblocks.first; block; block=block->next) { + mx= x; + my= y; + ui_window_to_block(ar, block, &mx, &my); + + for(but=block->buttons.first; but; but= but->next) { + if(ELEM3(but->type, LABEL, ROUNDBOX, SEPR)) + continue; + + if(ui_but_contains_pt(but, mx, my)) + /* give precedence to already activated buttons */ + if(!butover || (!butover->active && but->active)) + butover= but; + } + } + + return butover; +} + +/* ****************** button state handling **************************/ + +static int button_modal_state(uiHandleButtonState state) +{ + return ELEM6(state, BUTTON_STATE_WAIT_RELEASE, BUTTON_STATE_WAIT_KEY_EVENT, + BUTTON_STATE_NUM_EDITING, BUTTON_STATE_TEXT_EDITING, + BUTTON_STATE_TEXT_SELECTING, BUTTON_STATE_MENU_OPEN); +} + +static void button_tooltip_timer_reset(uiBut *but) +{ + uiHandleButtonData *data; + + data= but->active; + + if(data->tooltiptimer) { + WM_event_remove_window_timer(data->window, data->tooltiptimer); + data->tooltiptimer= NULL; + } + + if(U.flag & USER_TOOLTIPS) + if(!but->block->tooltipdisabled) + data->tooltiptimer= WM_event_add_window_timer(data->window, TIMER, BUTTON_TOOLTIP_DELAY); +} + +static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState state) +{ + uiHandleButtonData *data; + + data= but->active; + if(data->state == state) + return; + + /* highlight has timers for tooltips and auto open */ + if(state == BUTTON_STATE_HIGHLIGHT) { + but->flag &= ~UI_SELECT; + + button_tooltip_timer_reset(but); + + /* automatic open pulldown block timer */ + if(ELEM3(but->type, BLOCK, PULLDOWN, ICONTEXTROW)) { + if(!data->autoopentimer) { + int time; + + if(but->block->auto_open==2) time= 1; // test for toolbox + else if(but->block->flag & UI_BLOCK_LOOP || but->block->auto_open) time= 5*U.menuthreshold2; + else if(U.uiflag & USER_MENUOPENAUTO) time= 5*U.menuthreshold1; + else time= -1; + + if(time >= 0) + data->autoopentimer= WM_event_add_window_timer(data->window, TIMER, 0.02*(double)time); + } + } + } + else { + but->flag |= UI_SELECT; + + if(data->tooltiptimer) { + WM_event_remove_window_timer(data->window, data->tooltiptimer); + data->tooltiptimer= NULL; + } + if(data->tooltip) { + ui_tooltip_free(C, data->tooltip); + data->tooltip= NULL; + } + + if(data->autoopentimer) { + WM_event_remove_window_timer(data->window, data->autoopentimer); + data->autoopentimer= NULL; + } + } + + /* text editing */ + if(state == BUTTON_STATE_TEXT_EDITING && data->state != BUTTON_STATE_TEXT_SELECTING) + ui_textedit_begin(C, but, data); + else if(data->state == BUTTON_STATE_TEXT_EDITING && state != BUTTON_STATE_TEXT_SELECTING) + ui_textedit_end(C, but, data); + + /* number editing */ + if(state == BUTTON_STATE_NUM_EDITING) + ui_numedit_begin(but, data); + else if(data->state == BUTTON_STATE_NUM_EDITING) + ui_numedit_end(but, data); + + /* menu open */ + if(state == BUTTON_STATE_MENU_OPEN) + ui_blockopen_begin(C, but, data); + else if(data->state == BUTTON_STATE_MENU_OPEN) + ui_blockopen_end(C, but, data); + + /* add a short delay before exiting, to ensure there is some feedback */ + if(state == BUTTON_STATE_WAIT_FLASH) { + data->flashtimer= WM_event_add_window_timer(data->window, TIMER, BUTTON_FLASH_DELAY); + } + else if(data->flashtimer) { + WM_event_remove_window_timer(data->window, data->flashtimer); + data->flashtimer= NULL; + } + + /* add a blocking ui handler at the window handler for blocking, modal states + * but not for popups, because we already have a window level handler*/ + if(!(but->block->handle && but->block->handle->popup)) { + if(button_modal_state(state)) { + if(!button_modal_state(data->state)) + WM_event_add_ui_handler(C, &data->window->handlers, ui_handler_region_menu, NULL, data); + } + else { + if(button_modal_state(data->state)) + WM_event_remove_ui_handler(&data->window->handlers, ui_handler_region_menu, NULL, data); + } + } + + data->state= state; + + ui_check_but(but); + + /* redraw */ + ED_region_tag_redraw(data->region); +} + +static void button_activate_init(bContext *C, ARegion *ar, uiBut *but, uiButtonActivateType type) +{ + uiHandleButtonData *data; + + /* setup struct */ + data= MEM_callocN(sizeof(uiHandleButtonData), "uiHandleButtonData"); + data->window= CTX_wm_window(C); + data->region= ar; + if( ELEM(but->type, BUT_CURVE, SEARCH_MENU) ); // XXX curve is temp + else data->interactive= 1; + + data->state = BUTTON_STATE_INIT; + + /* activate button */ + but->flag |= UI_ACTIVE; + but->active= data; + + /* we disable auto_open in the block after a threshold, because we still + * want to allow auto opening adjacent menus even if no button is activated + * inbetween going over to the other button, but only for a short while */ + if(type == BUTTON_ACTIVATE_OVER && but->block->auto_open) + if(but->block->auto_open_last+BUTTON_AUTO_OPEN_THRESH < PIL_check_seconds_timer()) + but->block->auto_open= 0; + + button_activate_state(C, but, BUTTON_STATE_HIGHLIGHT); + + if(type == BUTTON_ACTIVATE_OPEN) { + button_activate_state(C, but, BUTTON_STATE_MENU_OPEN); + + /* activate first button in submenu */ + if(data->menu && data->menu->region) { + ARegion *subar= data->menu->region; + uiBlock *subblock= subar->uiblocks.first; + uiBut *subbut; + + if(subblock) { + subbut= ui_but_first(subblock); + + if(subbut) + ui_handle_button_activate(C, subar, subbut, BUTTON_ACTIVATE); + } + } + } + else if(type == BUTTON_ACTIVATE_TEXT_EDITING) + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + else if(type == BUTTON_ACTIVATE_APPLY) + button_activate_state(C, but, BUTTON_STATE_WAIT_FLASH); +} + +static void button_activate_exit(bContext *C, uiHandleButtonData *data, uiBut *but, int mousemove) +{ + uiBlock *block= but->block; + + /* ensure we are in the exit state */ + if(data->state != BUTTON_STATE_EXIT) + button_activate_state(C, but, BUTTON_STATE_EXIT); + + /* apply the button action or value */ + ui_apply_button(C, block, but, data, 0); + + /* if this button is in a menu, this will set the button return + * value to the button value and the menu return value to ok, the + * menu return value will be picked up and the menu will close */ + if(block->handle && !(block->flag & UI_BLOCK_KEEP_OPEN)) { + if(!data->cancel || data->escapecancel) { + uiPopupBlockHandle *menu; + + menu= block->handle; + menu->butretval= data->retval; + menu->menuretval= (data->cancel)? UI_RETURN_CANCEL: UI_RETURN_OK; + } + } + + /* disable tooltips until mousemove */ + ui_blocks_set_tooltips(data->region, 0); + + /* clean up */ + if(data->str) + MEM_freeN(data->str); + if(data->origstr) + MEM_freeN(data->origstr); + + /* redraw (data is but->active!) */ + ED_region_tag_redraw(data->region); + + /* clean up button */ + MEM_freeN(but->active); + but->active= NULL; + but->flag &= ~(UI_ACTIVE|UI_SELECT); + ui_check_but(but); + + /* adds empty mousemove in queue for re-init handler, in case mouse is + * still over a button. we cannot just check for this ourselfs because + * at this point the mouse may be over a button in another region */ + if(mousemove) + WM_event_add_mousemove(C); +} + +void ui_button_active_cancel(const bContext *C, uiBut *but) +{ + uiHandleButtonData *data; + + /* this gets called when the button somehow disappears while it is still + * active, this is bad for user interaction, but we need to handle this + * case cleanly anyway in case it happens */ + if(but->active) { + data= but->active; + data->cancel= 1; + button_activate_exit((bContext*)C, data, but, 0); + } +} + +/************** handle activating a button *************/ + +static uiBut *uit_but_find_open_event(ARegion *ar, wmEvent *event) +{ + uiBlock *block; + uiBut *but; + + for(block=ar->uiblocks.first; block; block=block->next) { + for(but=block->buttons.first; but; but= but->next) + if(but==event->customdata) + return but; + } + return NULL; +} + +static int ui_handle_button_over(bContext *C, wmEvent *event, ARegion *ar) +{ + uiBut *but; + + if(event->type == MOUSEMOVE) { + but= ui_but_find_mouse_over(ar, event->x, event->y); + if(but) + button_activate_init(C, ar, but, BUTTON_ACTIVATE_OVER); + } + else if(event->type == EVT_BUT_OPEN) { + but= uit_but_find_open_event(ar, event); + if(but) { + button_activate_init(C, ar, but, BUTTON_ACTIVATE_OVER); + ui_do_button(C, but->block, but, event); + } + } + + return WM_UI_HANDLER_CONTINUE; +} + +static void ui_handle_button_activate(bContext *C, ARegion *ar, uiBut *but, uiButtonActivateType type) +{ + uiBut *oldbut; + uiHandleButtonData *data; + + oldbut= ui_but_find_activated(ar); + if(oldbut) { + data= oldbut->active; + data->cancel= 1; + button_activate_exit(C, data, oldbut, 0); + } + + button_activate_init(C, ar, but, type); +} + +/************ handle events for an activated button ***********/ + +static int ui_handle_button_event(bContext *C, wmEvent *event, uiBut *but) +{ + uiHandleButtonData *data; + uiBlock *block; + ARegion *ar; + uiBut *postbut; + uiButtonActivateType posttype; + int retval; + + data= but->active; + block= but->block; + ar= data->region; + + retval= WM_UI_HANDLER_CONTINUE; + + if(data->state == BUTTON_STATE_HIGHLIGHT) { + switch(event->type) { + + case MOUSEMOVE: + /* verify if we are still over the button, if not exit */ + if(!ui_mouse_inside_button(ar, but, event->x, event->y)) { + data->cancel= 1; + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else if(event->x!=event->prevx || event->y!=event->prevy) { + /* re-enable tooltip on mouse move */ + ui_blocks_set_tooltips(ar, 1); + button_tooltip_timer_reset(but); + } + + break; + case TIMER: { + /* handle tooltip timer */ + if(event->customdata == data->tooltiptimer) { + WM_event_remove_window_timer(data->window, data->tooltiptimer); + data->tooltiptimer= NULL; + + if(!data->tooltip) + data->tooltip= ui_tooltip_create(C, data->region, but); + } + /* handle menu auto open timer */ + else if(event->customdata == data->autoopentimer) { + WM_event_remove_window_timer(data->window, data->autoopentimer); + data->autoopentimer= NULL; + + if(ui_mouse_inside_button(ar, but, event->x, event->y)) + button_activate_state(C, but, BUTTON_STATE_MENU_OPEN); + } + + retval= WM_UI_HANDLER_CONTINUE; + break; + case WHEELUPMOUSE: + case WHEELDOWNMOUSE: + case MIDDLEMOUSE: + /* XXX hardcoded keymap check... but anyway, while view changes, tooltips should be removed */ + if(data->tooltiptimer) { + WM_event_remove_window_timer(data->window, data->tooltiptimer); + data->tooltiptimer= NULL; + } + /* pass on purposedly */ + default: + /* handle button type specific events */ + retval= ui_do_button(C, block, but, event); + } + } + } + else if(data->state == BUTTON_STATE_WAIT_RELEASE) { + switch(event->type) { + case MOUSEMOVE: + /* deselect the button when moving the mouse away */ + if(ui_mouse_inside_button(ar, but, event->x, event->y)) { + if(!(but->flag & UI_SELECT)) { + but->flag |= UI_SELECT; + data->cancel= 0; + ED_region_tag_redraw(data->region); + } + } + else { + if(but->flag & UI_SELECT) { + but->flag &= ~UI_SELECT; + data->cancel= 1; + ED_region_tag_redraw(data->region); + } + } + break; + default: + /* otherwise catch mouse release event */ + ui_do_button(C, block, but, event); + break; + } + + retval= WM_UI_HANDLER_BREAK; + } + else if(data->state == BUTTON_STATE_WAIT_FLASH) { + switch(event->type) { + case TIMER: { + if(event->customdata == data->flashtimer) + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + + retval= WM_UI_HANDLER_CONTINUE; + } + else if(data->state == BUTTON_STATE_MENU_OPEN) { + switch(event->type) { + case MOUSEMOVE: { + uiBut *bt= ui_but_find_mouse_over(ar, event->x, event->y); + + if(bt && bt->active != data) { + data->cancel= 1; + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + break; + } + } + + ui_do_button(C, block, but, event); + retval= WM_UI_HANDLER_CONTINUE; + } + else { + ui_do_button(C, block, but, event); + retval= WM_UI_HANDLER_BREAK; + } + + if(data->state == BUTTON_STATE_EXIT) { + postbut= data->postbut; + posttype= data->posttype; + + button_activate_exit(C, data, but, (postbut == NULL)); + + /* for jumping to the next button with tab while text editing */ + if(postbut) + button_activate_init(C, ar, postbut, posttype); + } + + return retval; +} + +static void ui_handle_button_closed_submenu(bContext *C, wmEvent *event, uiBut *but) +{ + uiHandleButtonData *data; + uiPopupBlockHandle *menu; + + data= but->active; + menu= data->menu; + + /* copy over return values from the closing menu */ + if(menu->menuretval == UI_RETURN_OK) { + if(but->type == COL) + VECCOPY(data->vec, menu->retvec) + else if(ELEM3(but->type, MENU, ICONROW, ICONTEXTROW)) + data->value= menu->retvalue; + } + + /* now change button state or exit, which will close the submenu */ + if(ELEM(menu->menuretval, UI_RETURN_OK, UI_RETURN_CANCEL)) { + if(menu->menuretval != UI_RETURN_OK) + data->cancel= 1; + + button_activate_exit(C, data, but, 1); + } + else if(menu->menuretval == UI_RETURN_OUT) { + if(ui_mouse_inside_button(data->region, but, event->x, event->y)) { + button_activate_state(C, but, BUTTON_STATE_HIGHLIGHT); + } + else { + data->cancel= 1; + button_activate_exit(C, data, but, 1); + } + } +} + +/* ************************* menu handling *******************************/ + +/* function used to prevent loosing the open menu when using nested pulldowns, + * when moving mouse towards the pulldown menu over other buttons that could + * steal the highlight from the current button, only checks: + * + * - while mouse moves in triangular area defined old mouse position and + * left/right side of new menu + * - only for 1 second + */ + +static void ui_mouse_motion_towards_init(uiPopupBlockHandle *menu, int mx, int my, int force) +{ + if(!menu->dotowards || force) { + menu->dotowards= 1; + menu->towardsx= mx; + menu->towardsy= my; + + if(force) + menu->towardstime= DBL_MAX; /* unlimited time */ + else + menu->towardstime= PIL_check_seconds_timer(); + } +} + +static int ui_mouse_motion_towards_check(uiBlock *block, uiPopupBlockHandle *menu, int mx, int my) +{ + float p1[2], p2[2], p3[2], p4[2], oldp[2], newp[2]; + int closer; + + if(!menu->dotowards) return 0; + if((block->direction & UI_TOP) || (block->direction & UI_DOWN)) { + menu->dotowards= 0; + return menu->dotowards; + } + + /* verify that we are moving towards one of the edges of the + * menu block, in other words, in the triangle formed by the + * initial mouse location and two edge points. */ + p1[0]= block->minx-20; + p1[1]= block->miny-20; + + p2[0]= block->maxx+20; + p2[1]= block->miny-20; + + p3[0]= block->maxx+20; + p3[1]= block->maxy+20; + + p4[0]= block->minx-20; + p4[1]= block->maxy+20; + + oldp[0]= menu->towardsx; + oldp[1]= menu->towardsy; + + newp[0]= mx; + newp[1]= my; + + if(Vec2Lenf(oldp, newp) < 4.0f) + return menu->dotowards; + + closer= 0; + closer |= IsectPT2Df(newp, oldp, p1, p2); + closer |= IsectPT2Df(newp, oldp, p2, p3); + closer |= IsectPT2Df(newp, oldp, p3, p4); + closer |= IsectPT2Df(newp, oldp, p4, p1); + + if(!closer) + menu->dotowards= 0; + + /* 1 second timer */ + if(PIL_check_seconds_timer() - menu->towardstime > BUTTON_MOUSE_TOWARDS_THRESH) + menu->dotowards= 0; + + return menu->dotowards; +} + +int ui_handle_menu_event(bContext *C, wmEvent *event, uiPopupBlockHandle *menu, int topmenu) +{ + ARegion *ar; + uiBlock *block; + uiBut *but, *bt; + int inside, act, count, mx, my, retval; + + ar= menu->region; + block= ar->uiblocks.first; + + act= 0; + retval= WM_UI_HANDLER_CONTINUE; + + mx= event->x; + my= event->y; + ui_window_to_block(ar, block, &mx, &my); + + /* check if mouse is inside block */ + inside= 0; + if(block->minx <= mx && block->maxx >= mx) + if(block->miny <= my && block->maxy >= my) + inside= 1; + + if((but=ui_but_find_activated(ar)) && button_modal_state(but->active->state)) { + /* if a button is activated modal, always reset the start mouse + * position of the towards mechanism to avoid loosing focus, + * and don't handle events */ + ui_mouse_motion_towards_init(menu, mx, my, 1); + } + else if(event->type != TIMER) { + /* for ui_mouse_motion_towards_block */ + if(event->type == MOUSEMOVE) + ui_mouse_motion_towards_init(menu, mx, my, 0); + + switch(event->type) { + /* closing sublevels of pulldowns */ + case LEFTARROWKEY: + if(event->val==KM_PRESS && (block->flag & UI_BLOCK_LOOP)) + if(BLI_countlist(&block->saferct) > 0) + menu->menuretval= UI_RETURN_OUT; + + retval= WM_UI_HANDLER_BREAK; + break; + + /* opening sublevels of pulldowns */ + case RIGHTARROWKEY: + if(event->val==KM_PRESS && (block->flag & UI_BLOCK_LOOP)) { + but= ui_but_find_activated(ar); + + if(!but) { + /* no item active, we make first active */ + if(block->direction & UI_TOP) but= ui_but_last(block); + else but= ui_but_first(block); + } + + if(but && ELEM(but->type, BLOCK, PULLDOWN)) + ui_handle_button_activate(C, ar, but, BUTTON_ACTIVATE_OPEN); + } + + retval= WM_UI_HANDLER_BREAK; + break; + + case UPARROWKEY: + case DOWNARROWKEY: + case WHEELUPMOUSE: + case WHEELDOWNMOUSE: + /* arrowkeys: only handle for block_loop blocks */ + if(inside || (block->flag & UI_BLOCK_LOOP)) { + if(event->val==KM_PRESS) { + but= ui_but_find_activated(ar); + if(but) { + if(ELEM(event->type, DOWNARROWKEY, WHEELDOWNMOUSE)) { + if(block->direction & UI_TOP) but= ui_but_prev(but); + else but= ui_but_next(but); + } + else { + if(block->direction & UI_TOP) but= ui_but_next(but); + else but= ui_but_prev(but); + } + + if(but) + ui_handle_button_activate(C, ar, but, BUTTON_ACTIVATE); + } + + if(!but) { + if(ELEM(event->type, UPARROWKEY, WHEELUPMOUSE)) { + if(block->direction & UI_TOP) bt= ui_but_first(block); + else bt= ui_but_last(block); + } + else { + if(block->direction & UI_TOP) bt= ui_but_last(block); + else bt= ui_but_first(block); + } + + if(bt) + ui_handle_button_activate(C, ar, bt, BUTTON_ACTIVATE); + } + } + } + + retval= WM_UI_HANDLER_BREAK; + break; + + case ONEKEY: case PAD1: + act= 1; + case TWOKEY: case PAD2: + if(act==0) act= 2; + case THREEKEY: case PAD3: + if(act==0) act= 3; + case FOURKEY: case PAD4: + if(act==0) act= 4; + case FIVEKEY: case PAD5: + if(act==0) act= 5; + case SIXKEY: case PAD6: + if(act==0) act= 6; + case SEVENKEY: case PAD7: + if(act==0) act= 7; + case EIGHTKEY: case PAD8: + if(act==0) act= 8; + case NINEKEY: case PAD9: + if(act==0) act= 9; + case ZEROKEY: case PAD0: + if(act==0) act= 10; + + if(block->flag & UI_BLOCK_NUMSELECT) { + if(event->alt) act+= 10; + + count= 0; + for(but= block->buttons.first; but; but= but->next) { + int doit= 0; + + if(but->type!=LABEL && but->type!=SEPR) + count++; + + /* exception for menus like layer buts, with button aligning they're not drawn in order */ + if(but->type==TOGR) { + if(but->bitnr==act-1) + doit= 1; + } + else if(count==act) + doit=1; + + if(doit) { + ui_handle_button_activate(C, ar, but, BUTTON_ACTIVATE_APPLY); + break; + } + } + } + + retval= WM_UI_HANDLER_BREAK; + break; + } + + /* here we check return conditions for menus */ + if(block->flag & UI_BLOCK_LOOP) { + /* if we click outside the block, verify if we clicked on the + * button that opened us, otherwise we need to close */ + if(inside==0) { + uiSafetyRct *saferct= block->saferct.first; + + if(ELEM3(event->type, LEFTMOUSE, MIDDLEMOUSE, RIGHTMOUSE) && event->val==KM_PRESS) + if(saferct && !BLI_in_rctf(&saferct->parent, event->x, event->y)) + menu->menuretval= UI_RETURN_OK; + } + + if(menu->menuretval); + else if(event->type==ESCKEY && event->val==KM_PRESS) { + /* esc cancels this and all preceding menus */ + menu->menuretval= UI_RETURN_CANCEL; + } + else if(ELEM(event->type, RETKEY, PADENTER) && event->val==KM_PRESS) { + /* enter will always close this block, but we let the event + * get handled by the button if it is activated */ + if(!ui_but_find_activated(ar)) + menu->menuretval= UI_RETURN_OK; + } + else { + ui_mouse_motion_towards_check(block, menu, mx, my); + + /* check mouse moving outside of the menu */ + if(inside==0 && (block->flag & UI_BLOCK_MOVEMOUSE_QUIT)) { + uiSafetyRct *saferct; + + /* check for all parent rects, enables arrowkeys to be used */ + for(saferct=block->saferct.first; saferct; saferct= saferct->next) { + /* for mouse move we only check our own rect, for other + * events we check all preceding block rects too to make + * arrow keys navigation work */ + if(event->type!=MOUSEMOVE || saferct==block->saferct.first) { + if(BLI_in_rctf(&saferct->parent, (float)event->x, (float)event->y)) + break; + if(BLI_in_rctf(&saferct->safety, (float)event->x, (float)event->y)) + break; + } + } + + /* strict check, and include the parent rect */ + if(!menu->dotowards && !saferct) + menu->menuretval= (block->flag & UI_BLOCK_KEEP_OPEN)? UI_RETURN_OK: UI_RETURN_OUT; + else if(menu->dotowards && event->type==MOUSEMOVE) + retval= WM_UI_HANDLER_BREAK; + } + } + } + } + + /* if we are didn't handle the event yet, lets pass it on to + * buttons inside this region. disabled inside check .. not sure + * anymore why it was there? but i meant enter enter didn't work + * for example when mouse was not over submenu */ + if((/*inside &&*/ !menu->menuretval && retval == WM_UI_HANDLER_CONTINUE) || event->type == TIMER) { + but= ui_but_find_activated(ar); + + if(but) { + ScrArea *ctx_area= CTX_wm_area(C); + ARegion *ctx_region= CTX_wm_region(C); + + if(menu->ctx_area) CTX_wm_area_set(C, menu->ctx_area); + if(menu->ctx_region) CTX_wm_region_set(C, menu->ctx_region); + + retval= ui_handle_button_event(C, event, but); + + if(menu->ctx_area) CTX_wm_area_set(C, ctx_area); + if(menu->ctx_region) CTX_wm_region_set(C, ctx_region); + } + else + retval= ui_handle_button_over(C, event, ar); + } + + /* if we set a menu return value, ensure we continue passing this on to + * lower menus and buttons, so always set continue then, and if we are + * inside the region otherwise, ensure we swallow the event */ + if(menu->menuretval) + return WM_UI_HANDLER_CONTINUE; + else if(inside) + return WM_UI_HANDLER_BREAK; + else + return retval; +} + +static int ui_handle_menu_closed_submenu(bContext *C, wmEvent *event, uiPopupBlockHandle *menu) +{ + ARegion *ar; + uiBut *but; + uiBlock *block; + uiHandleButtonData *data; + uiPopupBlockHandle *submenu; + int mx, my; + + ar= menu->region; + block= ar->uiblocks.first; + + but= ui_but_find_activated(ar); + data= but->active; + submenu= data->menu; + + if(submenu->menuretval) { + /* first decide if we want to close our own menu cascading, if + * so pass on the sub menu return value to our own menu handle */ + if(ELEM(submenu->menuretval, UI_RETURN_OK, UI_RETURN_CANCEL)) { + if(!(block->flag & UI_BLOCK_KEEP_OPEN)) { + menu->menuretval= submenu->menuretval; + menu->butretval= data->retval; + } + } + + /* now let activated button in this menu exit, which + * will actually close the submenu too */ + ui_handle_button_closed_submenu(C, event, but); + } + + /* for cases where close does not cascade, allow the user to + * move the mouse back towards the menu without closing */ + mx= event->x; + my= event->y; + ui_window_to_block(ar, block, &mx, &my); + ui_mouse_motion_towards_init(menu, mx, my, 1); + + if(menu->menuretval) + return WM_UI_HANDLER_CONTINUE; + else + return WM_UI_HANDLER_BREAK; +} + +static int ui_handle_menus_recursive(bContext *C, wmEvent *event, uiPopupBlockHandle *menu) +{ + uiBut *but; + uiHandleButtonData *data; + uiPopupBlockHandle *submenu; + int retval= WM_UI_HANDLER_CONTINUE; + + /* check if we have a submenu, and handle events for it first */ + but= ui_but_find_activated(menu->region); + data= (but)? but->active: NULL; + submenu= (data)? data->menu: NULL; + + if(submenu) + retval= ui_handle_menus_recursive(C, event, submenu); + + /* now handle events for our own menu */ + if(retval == WM_UI_HANDLER_CONTINUE || event->type == TIMER) { + if(submenu && submenu->menuretval) + retval= ui_handle_menu_closed_submenu(C, event, menu); + else + retval= ui_handle_menu_event(C, event, menu, (submenu == NULL)); + } + + return retval; +} + +/* *************** UI event handlers **************** */ + +static int ui_handler_region(bContext *C, wmEvent *event, void *userdata) +{ + ARegion *ar; + uiBut *but; + int retval; + + /* here we handle buttons at the region level, non-modal */ + ar= CTX_wm_region(C); + retval= WM_UI_HANDLER_CONTINUE; + + if(ar==NULL) return retval; + if(ar->uiblocks.first==NULL) return retval; + + /* either handle events for already activated button or try to activate */ + but= ui_but_find_activated(ar); + + if(!but || !button_modal_state(but->active->state)) + retval= ui_handler_panel_region(C, event); + + if(retval == WM_UI_HANDLER_CONTINUE) { + if(but) + retval= ui_handle_button_event(C, event, but); + else + retval= ui_handle_button_over(C, event, ar); + } + + /* re-enable tooltips */ + if(event->type == MOUSEMOVE && (event->x!=event->prevx || event->y!=event->prevy)) + ui_blocks_set_tooltips(ar, 1); + + /* delayed apply callbacks */ + ui_apply_but_funcs_after(C); + + return retval; +} + +static void ui_handler_remove_region(bContext *C, void *userdata) +{ + bScreen *sc; + ARegion *ar; + + ar= CTX_wm_region(C); + if(ar == NULL) return; + + uiFreeBlocks(C, &ar->uiblocks); + + sc= CTX_wm_screen(C); + if(sc == NULL) return; + + /* delayed apply callbacks, but not for screen level regions, those + * we rather do at the very end after closing them all, which will + * be done in ui_handler_region/window */ + if(BLI_findindex(&sc->regionbase, ar) == -1) + ui_apply_but_funcs_after(C); +} + +static int ui_handler_region_menu(bContext *C, wmEvent *event, void *userdata) +{ + ARegion *ar; + uiBut *but; + uiHandleButtonData *data; + int retval; + + /* here we handle buttons at the window level, modal, for example + * while number sliding, text editing, or when a menu block is open */ + ar= CTX_wm_menu(C); + if(!ar) + ar= CTX_wm_region(C); + + but= ui_but_find_activated(ar); + + if(but) { + /* handle activated button events */ + data= but->active; + + if(data->state == BUTTON_STATE_MENU_OPEN) { + /* handle events for menus and their buttons recursively, + * this will handle events from the top to the bottom menu */ + retval= ui_handle_menus_recursive(C, event, data->menu); + + /* handle events for the activated button */ + if(retval == WM_UI_HANDLER_CONTINUE || event->type == TIMER) { + if(data->menu->menuretval) + ui_handle_button_closed_submenu(C, event, but); + else + ui_handle_button_event(C, event, but); + } + } + else { + /* handle events for the activated button */ + ui_handle_button_event(C, event, but); + } + } + + /* re-enable tooltips */ + if(event->type == MOUSEMOVE && (event->x!=event->prevx || event->y!=event->prevy)) + ui_blocks_set_tooltips(ar, 1); + + /* delayed apply callbacks */ + ui_apply_but_funcs_after(C); + + /* we block all events, this is modal interaction */ + return WM_UI_HANDLER_BREAK; +} + +/* two types of popups, one with operator + enum, other with regular callbacks */ +static int ui_handler_popup(bContext *C, wmEvent *event, void *userdata) +{ + uiPopupBlockHandle *menu= userdata; + + ui_handle_menus_recursive(C, event, menu); + + /* free if done, does not free handle itself */ + if(menu->menuretval) { + /* copy values, we have to free first (closes region) */ + uiPopupBlockHandle temp= *menu; + + ui_popup_block_free(C, menu); + WM_event_remove_ui_handler(&CTX_wm_window(C)->handlers, ui_handler_popup, ui_handler_remove_popup, menu); + + if(temp.menuretval == UI_RETURN_OK) { + if(temp.popup_func) + temp.popup_func(C, temp.popup_arg, temp.retvalue); + if(temp.optype) + WM_operator_name_call(C, temp.optype->idname, temp.opcontext, NULL); + } + else if(temp.cancel_func) + temp.cancel_func(temp.popup_arg); + } + else { + /* re-enable tooltips */ + if(event->type == MOUSEMOVE && (event->x!=event->prevx || event->y!=event->prevy)) + ui_blocks_set_tooltips(menu->region, 1); + } + + /* delayed apply callbacks */ + ui_apply_but_funcs_after(C); + + /* we block all events, this is modal interaction */ + return WM_UI_HANDLER_BREAK; +} + +static void ui_handler_remove_popup(bContext *C, void *userdata) +{ + uiPopupBlockHandle *menu= userdata; + + /* free menu block if window is closed for some reason */ + ui_popup_block_free(C, menu); + + /* delayed apply callbacks */ + ui_apply_but_funcs_after(C); +} + +void UI_add_region_handlers(ListBase *handlers) +{ + WM_event_remove_ui_handler(handlers, ui_handler_region, ui_handler_remove_region, NULL); + WM_event_add_ui_handler(NULL, handlers, ui_handler_region, ui_handler_remove_region, NULL); +} + +void UI_add_popup_handlers(bContext *C, ListBase *handlers, uiPopupBlockHandle *menu) +{ + WM_event_add_ui_handler(C, handlers, ui_handler_popup, ui_handler_remove_popup, menu); +} + diff --git a/source/blender/editors/interface/interface_icons.c b/source/blender/editors/interface/interface_icons.c new file mode 100644 index 00000000000..df069069a33 --- /dev/null +++ b/source/blender/editors/interface/interface_icons.c @@ -0,0 +1,977 @@ +/** + * $Id$ + * + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + * + * Contributors: Blender Foundation, full recode + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include <math.h> +#include <stdlib.h> +#include <string.h> + +#ifndef WIN32 +#include <unistd.h> +#else +#include <io.h> +#include <direct.h> +#endif +#include "MEM_guardedalloc.h" + +#include "BLI_arithb.h" +#include "BLI_blenlib.h" +#include "BLI_storage_types.h" + +#include "DNA_screen_types.h" +#include "DNA_userdef_types.h" + + +#include "BKE_utildefines.h" +#include "BKE_image.h" +#include "BKE_icons.h" + +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" + +#include "BIF_gl.h" +#include "BIF_glutil.h" +#include "UI_interface.h" +#include "UI_interface_icons.h" + +// XXX #include "BIF_previewrender.h" +// XXX #include "BIF_screen.h" + +#include "UI_resources.h" /* elubie: should be removed once the enum for the ICONS is in BIF_preview_icons.h */ +#include "interface_intern.h" +#include "ED_datafiles.h" + +#define ICON_IMAGE_W 600 +#define ICON_IMAGE_H 640 + +#define ICON_GRID_COLS 26 +#define ICON_GRID_ROWS 30 + +#define ICON_GRID_MARGIN 5 +#define ICON_GRID_W 16 +#define ICON_GRID_H 16 + +typedef struct IconImage { + int w; + int h; + unsigned int *rect; +} IconImage; + +typedef void (*VectorDrawFunc)(int x, int y, int w, int h, float alpha); + +typedef struct DrawInfo { + int w; + int h; + float aspect; + VectorDrawFunc drawFunc; /* If drawFunc is defined then it is a vector icon, otherwise use rect */ + IconImage* icon; +} DrawInfo; + +/* ******************* STATIC LOCAL VARS ******************* */ +/* static here to cache results of icon directory scan, so it's not + * scanning the filesystem each time the menu is drawn */ +static struct ListBase iconfilelist = {0, 0}; + + +/* **************************************************** */ + +static void def_internal_icon(ImBuf *bbuf, int icon_id, int xofs, int yofs, int size) +{ + Icon *new_icon = NULL; + IconImage *iimg = NULL; + DrawInfo *di; + int y = 0; + int imgsize = 0; + + new_icon = MEM_callocN(sizeof(Icon), "texicon"); + + new_icon->obj = 0; /* icon is not for library object */ + new_icon->type = 0; + + di = MEM_callocN(sizeof(DrawInfo), "drawinfo"); + di->drawFunc = 0; + di->w = size; + di->h = size; + di->aspect = 1.0f; + + iimg = MEM_mallocN(sizeof(IconImage), "icon_img"); + iimg->rect = MEM_mallocN(size*size*sizeof(unsigned int), "icon_rect"); + iimg->w = size; + iimg->h = size; + + /* Here we store the rect in the icon - same as before */ + imgsize = bbuf->x; + for (y=0; y<size; y++) { + memcpy(&iimg->rect[y*size], &bbuf->rect[(y+yofs)*imgsize+xofs], size*sizeof(int)); + } + + di->icon = iimg; + + new_icon->drawinfo_free = UI_icons_free_drawinfo; + new_icon->drawinfo = di; + + BKE_icon_set(icon_id, new_icon); +} + +static void def_internal_vicon( int icon_id, VectorDrawFunc drawFunc) +{ + Icon *new_icon = NULL; + DrawInfo* di; + + new_icon = MEM_callocN(sizeof(Icon), "texicon"); + + new_icon->obj = 0; /* icon is not for library object */ + new_icon->type = 0; + + di = MEM_callocN(sizeof(DrawInfo), "drawinfo"); + di->drawFunc =drawFunc; + di->w = ICON_DEFAULT_HEIGHT; + di->h = ICON_DEFAULT_HEIGHT; + di->aspect = 1.0f; + di->icon = NULL; + + new_icon->drawinfo_free = 0; + new_icon->drawinfo = di; + + BKE_icon_set(icon_id, new_icon); +} + +/* Vector Icon Drawing Routines */ + + /* Utilities */ + +static void viconutil_set_point(GLint pt[2], int x, int y) +{ + pt[0] = x; + pt[1] = y; +} + +static void viconutil_draw_tri(GLint (*pts)[2]) +{ + glBegin(GL_TRIANGLES); + glVertex2iv(pts[0]); + glVertex2iv(pts[1]); + glVertex2iv(pts[2]); + glEnd(); +} + +static void viconutil_draw_lineloop(GLint (*pts)[2], int numPoints) +{ + int i; + + glBegin(GL_LINE_LOOP); + for (i=0; i<numPoints; i++) { + glVertex2iv(pts[i]); + } + glEnd(); +} + +static void viconutil_draw_lineloop_smooth(GLint (*pts)[2], int numPoints) +{ + glEnable(GL_LINE_SMOOTH); + viconutil_draw_lineloop(pts, numPoints); + glDisable(GL_LINE_SMOOTH); +} + +static void viconutil_draw_points(GLint (*pts)[2], int numPoints, int pointSize) +{ + int i; + + glBegin(GL_QUADS); + for (i=0; i<numPoints; i++) { + int x = pts[i][0], y = pts[i][1]; + + glVertex2i(x-pointSize,y-pointSize); + glVertex2i(x+pointSize,y-pointSize); + glVertex2i(x+pointSize,y+pointSize); + glVertex2i(x-pointSize,y+pointSize); + } + glEnd(); +} + + /* Drawing functions */ + +static void vicon_x_draw(int x, int y, int w, int h, float alpha) +{ + x += 3; + y += 3; + w -= 6; + h -= 6; + + glEnable( GL_LINE_SMOOTH ); + + glLineWidth(2.5); + + glColor4f(0.0, 0.0, 0.0, alpha); + glBegin(GL_LINES); + glVertex2i(x ,y ); + glVertex2i(x+w,y+h); + glVertex2i(x+w,y ); + glVertex2i(x ,y+h); + glEnd(); + + glLineWidth(1.0); + + glDisable( GL_LINE_SMOOTH ); +} + +static void vicon_view3d_draw(int x, int y, int w, int h, float alpha) +{ + int cx = x + w/2; + int cy = y + h/2; + int d = MAX2(2, h/3); + + glColor4f(0.5, 0.5, 0.5, alpha); + glBegin(GL_LINES); + glVertex2i(x , cy-d); + glVertex2i(x+w, cy-d); + glVertex2i(x , cy+d); + glVertex2i(x+w, cy+d); + + glVertex2i(cx-d, y ); + glVertex2i(cx-d, y+h); + glVertex2i(cx+d, y ); + glVertex2i(cx+d, y+h); + glEnd(); + + glColor4f(0.0, 0.0, 0.0, alpha); + glBegin(GL_LINES); + glVertex2i(x , cy); + glVertex2i(x+w, cy); + glVertex2i(cx, y ); + glVertex2i(cx, y+h); + glEnd(); +} + +static void vicon_edit_draw(int x, int y, int w, int h, float alpha) +{ + GLint pts[4][2]; + + viconutil_set_point(pts[0], x+3 , y+3 ); + viconutil_set_point(pts[1], x+w-3, y+3 ); + viconutil_set_point(pts[2], x+w-3, y+h-3); + viconutil_set_point(pts[3], x+3 , y+h-3); + + glColor4f(0.0, 0.0, 0.0, alpha); + viconutil_draw_lineloop(pts, 4); + + glColor3f(1, 1, 0.0); + viconutil_draw_points(pts, 4, 1); +} + +static void vicon_editmode_hlt_draw(int x, int y, int w, int h, float alpha) +{ + GLint pts[3][2]; + + viconutil_set_point(pts[0], x+w/2, y+h-2); + viconutil_set_point(pts[1], x+3, y+4); + viconutil_set_point(pts[2], x+w-3, y+4); + + glColor4f(0.5, 0.5, 0.5, alpha); + viconutil_draw_tri(pts); + + glColor4f(0.0, 0.0, 0.0, 1); + viconutil_draw_lineloop_smooth(pts, 3); + + glColor3f(1, 1, 0.0); + viconutil_draw_points(pts, 3, 1); +} + +static void vicon_editmode_dehlt_draw(int x, int y, int w, int h, float alpha) +{ + GLint pts[3][2]; + + viconutil_set_point(pts[0], x+w/2, y+h-2); + viconutil_set_point(pts[1], x+3, y+4); + viconutil_set_point(pts[2], x+w-3, y+4); + + glColor4f(0.0f, 0.0f, 0.0f, 1); + viconutil_draw_lineloop_smooth(pts, 3); + + glColor3f(.9f, .9f, .9f); + viconutil_draw_points(pts, 3, 1); +} + +static void vicon_disclosure_tri_right_draw(int x, int y, int w, int h, float alpha) +{ + GLint pts[3][2]; + int cx = x+w/2; + int cy = y+w/2; + int d = w/3, d2 = w/5; + + viconutil_set_point(pts[0], cx-d2, cy+d); + viconutil_set_point(pts[1], cx-d2, cy-d); + viconutil_set_point(pts[2], cx+d2, cy); + + glShadeModel(GL_SMOOTH); + glBegin(GL_TRIANGLES); + glColor4f(0.8f, 0.8f, 0.8f, alpha); + glVertex2iv(pts[0]); + glVertex2iv(pts[1]); + glColor4f(0.3f, 0.3f, 0.3f, alpha); + glVertex2iv(pts[2]); + glEnd(); + glShadeModel(GL_FLAT); + + glColor4f(0.0f, 0.0f, 0.0f, 1); + viconutil_draw_lineloop_smooth(pts, 3); +} + +static void vicon_disclosure_tri_down_draw(int x, int y, int w, int h, float alpha) +{ + GLint pts[3][2]; + int cx = x+w/2; + int cy = y+w/2; + int d = w/3, d2 = w/5; + + viconutil_set_point(pts[0], cx+d, cy+d2); + viconutil_set_point(pts[1], cx-d, cy+d2); + viconutil_set_point(pts[2], cx, cy-d2); + + glShadeModel(GL_SMOOTH); + glBegin(GL_TRIANGLES); + glColor4f(0.8f, 0.8f, 0.8f, alpha); + glVertex2iv(pts[0]); + glVertex2iv(pts[1]); + glColor4f(0.3f, 0.3f, 0.3f, alpha); + glVertex2iv(pts[2]); + glEnd(); + glShadeModel(GL_FLAT); + + glColor4f(0.0f, 0.0f, 0.0f, 1); + viconutil_draw_lineloop_smooth(pts, 3); +} + +static void vicon_move_up_draw(int x, int y, int w, int h, float alpha) +{ + int d=-2; + + glEnable(GL_LINE_SMOOTH); + glLineWidth(1); + glColor3f(0.0, 0.0, 0.0); + + glBegin(GL_LINE_STRIP); + glVertex2i(x+w/2-d*2, y+h/2+d); + glVertex2i(x+w/2, y+h/2-d + 1); + glVertex2i(x+w/2+d*2, y+h/2+d); + glEnd(); + + glLineWidth(1.0); + glDisable(GL_LINE_SMOOTH); +} + +static void vicon_move_down_draw(int x, int y, int w, int h, float alpha) +{ + int d=2; + + glEnable(GL_LINE_SMOOTH); + glLineWidth(1); + glColor3f(0.0, 0.0, 0.0); + + glBegin(GL_LINE_STRIP); + glVertex2i(x+w/2-d*2, y+h/2+d); + glVertex2i(x+w/2, y+h/2-d - 1); + glVertex2i(x+w/2+d*2, y+h/2+d); + glEnd(); + + glLineWidth(1.0); + glDisable(GL_LINE_SMOOTH); +} + +static void init_internal_icons() +{ + bTheme *btheme= U.themes.first; + ImBuf *bbuf= NULL; + int x, y; + char iconfilestr[FILE_MAXDIR+FILE_MAXFILE]; + char filenamestr[FILE_MAXFILE+16]; // 16 == strlen(".blender/icons/")+1 + + if ((btheme!=NULL) && (strlen(btheme->tui.iconfile) > 0)) { + +#ifdef WIN32 + sprintf(filenamestr, "icons/%s", btheme->tui.iconfile); +#else + sprintf(filenamestr, ".blender/icons/%s", btheme->tui.iconfile); +#endif + + BLI_make_file_string("/", iconfilestr, BLI_gethome(), filenamestr); + + if (BLI_exists(iconfilestr)) { + bbuf = IMB_loadiffname(iconfilestr, IB_rect); + if(bbuf->x < ICON_IMAGE_W || bbuf->y < ICON_IMAGE_H) { + printf("\n***WARNING***\nIcons file %s too small.\nUsing built-in Icons instead\n", iconfilestr); + IMB_freeImBuf(bbuf); + bbuf= NULL; + } + } + } + if(bbuf==NULL) + bbuf = IMB_ibImageFromMemory((int *)datatoc_blenderbuttons, datatoc_blenderbuttons_size, IB_rect); + + for (y=0; y<ICON_GRID_ROWS; y++) { + for (x=0; x<ICON_GRID_COLS; x++) { + def_internal_icon(bbuf, BIFICONID_FIRST + y*ICON_GRID_COLS + x, + x*(ICON_GRID_W+ICON_GRID_MARGIN)+ICON_GRID_MARGIN, + y*(ICON_GRID_H+ICON_GRID_MARGIN)+ICON_GRID_MARGIN, ICON_GRID_W); + } + } + + def_internal_vicon(VICON_VIEW3D, vicon_view3d_draw); + def_internal_vicon(VICON_EDIT, vicon_edit_draw); + def_internal_vicon(VICON_EDITMODE_DEHLT, vicon_editmode_dehlt_draw); + def_internal_vicon(VICON_EDITMODE_HLT, vicon_editmode_hlt_draw); + def_internal_vicon(VICON_DISCLOSURE_TRI_RIGHT, vicon_disclosure_tri_right_draw); + def_internal_vicon(VICON_DISCLOSURE_TRI_DOWN, vicon_disclosure_tri_down_draw); + def_internal_vicon(VICON_MOVE_UP, vicon_move_up_draw); + def_internal_vicon(VICON_MOVE_DOWN, vicon_move_down_draw); + def_internal_vicon(VICON_X, vicon_x_draw); + + IMB_freeImBuf(bbuf); +} + + +static void init_iconfile_list(struct ListBase *list) +{ + IconFile *ifile; + ImBuf *bbuf= NULL; + struct direntry *dir; + int restoredir = 1; /* restore to current directory */ + int totfile, i, index=1; + int ifilex, ifiley; + char icondirstr[FILE_MAX]; + char iconfilestr[FILE_MAX+16]; /* allow 256 chars for file+dir */ + char olddir[FILE_MAX]; + + list->first = list->last = NULL; + +#ifdef WIN32 + BLI_make_file_string("/", icondirstr, BLI_gethome(), "icons"); +#else + BLI_make_file_string("/", icondirstr, BLI_gethome(), ".blender/icons"); +#endif + + if(BLI_exists(icondirstr)==0) + return; + + /* since BLI_getdir changes the current working directory, restore it + back to old value afterwards */ + if(!BLI_getwdN(olddir)) + restoredir = 0; + totfile = BLI_getdir(icondirstr, &dir); + if (restoredir) + chdir(olddir); + + for(i=0; i<totfile; i++) { + if( (dir[i].type & S_IFREG) ) { + char *filename = dir[i].relname; + + if(BLI_testextensie(filename, ".png")) { + + /* check to see if the image is the right size, continue if not */ + /* copying strings here should go ok, assuming that we never get back + a complete path to file longer than 256 chars */ + sprintf(iconfilestr, "%s/%s", icondirstr, filename); + if(BLI_exists(iconfilestr)) bbuf = IMB_loadiffname(iconfilestr, IB_rect); + + ifilex = bbuf->x; + ifiley = bbuf->y; + IMB_freeImBuf(bbuf); + + if ((ifilex != ICON_IMAGE_W) || (ifiley != ICON_IMAGE_H)) + continue; + + /* found a potential icon file, so make an entry for it in the cache list */ + ifile = MEM_callocN(sizeof(IconFile), "IconFile"); + + BLI_strncpy(ifile->filename, filename, sizeof(ifile->filename)); + ifile->index = index; + + BLI_addtail(list, ifile); + + index++; + } + } + } + + /* free temporary direntry structure that's been created by BLI_getdir() */ + i= totfile-1; + + for(; i>=0; i--){ + MEM_freeN(dir[i].relname); + if (dir[i].string) MEM_freeN(dir[i].string); + } + free(dir); + dir= 0; +} + +static void free_iconfile_list(struct ListBase *list) +{ + IconFile *ifile=NULL, *next_ifile=NULL; + + for(ifile=list->first; ifile; ifile=next_ifile) { + next_ifile = ifile->next; + BLI_freelinkN(list, ifile); + } +} + +int UI_iconfile_get_index(char *filename) +{ + IconFile *ifile; + ListBase *list=&(iconfilelist); + + for(ifile=list->first; ifile; ifile=ifile->next) { + if ( BLI_streq(filename, ifile->filename)) { + return ifile->index; + } + } + + return 0; +} + +ListBase *UI_iconfile_list(void) +{ + ListBase *list=&(iconfilelist); + + return list; +} + + +void UI_icons_free() +{ + free_iconfile_list(&iconfilelist); + BKE_icons_free(); +} + +void UI_icons_free_drawinfo(void *drawinfo) +{ + DrawInfo *di = drawinfo; + + if (di) + { + if (di->icon) { + MEM_freeN(di->icon->rect); + MEM_freeN(di->icon); + } + MEM_freeN(di); + } +} + +static DrawInfo *icon_create_drawinfo() +{ + DrawInfo *di = NULL; + + di = MEM_callocN(sizeof(DrawInfo), "di_icon"); + + di->drawFunc = 0; + di->w = ICON_DEFAULT_HEIGHT; + di->h = ICON_DEFAULT_HEIGHT; + di->icon = NULL; + di->aspect = 1.0f; + + return di; +} + +int UI_icon_get_width(int icon_id) +{ + Icon *icon = NULL; + DrawInfo *di = NULL; + + icon = BKE_icon_get(icon_id); + + if (!icon) { + printf("UI_icon_get_width: Internal error, no icon for icon ID: %d\n", icon_id); + return 0; + } + + di = (DrawInfo *)icon->drawinfo; + if (!di) { + di = icon_create_drawinfo(); + icon->drawinfo = di; + } + + if (di) + return di->w; + + return 0; +} + +int UI_icon_get_height(int icon_id) +{ + Icon *icon = NULL; + DrawInfo *di = NULL; + + icon = BKE_icon_get(icon_id); + + if (!icon) { + printf("UI_icon_get_height: Internal error, no icon for icon ID: %d\n", icon_id); + return 0; + } + + di = (DrawInfo*)icon->drawinfo; + + if (!di) { + di = icon_create_drawinfo(); + icon->drawinfo = di; + } + + if (di) + return di->h; + + return 0; +} + +void UI_icons_init(int first_dyn_id) +{ + init_iconfile_list(&iconfilelist); + BKE_icons_init(first_dyn_id); + init_internal_icons(); +} + +#if 0 +static void icon_copy_rect(ImBuf *ibuf, unsigned int w, unsigned int h, unsigned int *rect) +{ + struct ImBuf *ima; + unsigned int *drect, *srect; + float scaledx, scaledy; + short ex, ey, dx, dy; + + /* paranoia test */ + if(ibuf==NULL || (ibuf->rect==NULL && ibuf->rect_float==NULL)) + return; + + /* waste of cpu cyles... but the imbuf API has no other way to scale fast (ton) */ + ima = IMB_dupImBuf(ibuf); + + if (!ima) + return; + + if (ima->x > ima->y) { + scaledx = (float)w; + scaledy = ( (float)ima->y/(float)ima->x )*(float)w; + } + else { + scaledx = ( (float)ima->x/(float)ima->y )*(float)h; + scaledy = (float)h; + } + + ex = (short)scaledx; + ey = (short)scaledy; + + dx = (w - ex) / 2; + dy = (h - ey) / 2; + + IMB_scalefastImBuf(ima, ex, ey); + + /* if needed, convert to 32 bits */ + if(ima->rect==NULL) + IMB_rect_from_float(ima); + + srect = ima->rect; + drect = rect; + + drect+= dy*w+dx; + for (;ey > 0; ey--){ + memcpy(drect,srect, ex * sizeof(int)); + drect += w; + srect += ima->x; + } + IMB_freeImBuf(ima); +} + +/* Render size for preview images at level miplevel */ +static int preview_render_size(int miplevel) +{ + switch (miplevel) { + case 0: return 32; + case 1: return PREVIEW_DEFAULT_HEIGHT; + } + return 0; +} + +static void icon_create_mipmap(struct PreviewImage* prv_img, int miplevel) +{ + unsigned int size = preview_render_size(miplevel); + + if (!prv_img) { + printf("Error: requested preview image does not exist"); + } + if (!prv_img->rect[miplevel]) { + prv_img->w[miplevel] = size; + prv_img->h[miplevel] = size; + prv_img->changed[miplevel] = 1; + prv_img->rect[miplevel] = MEM_callocN(size*size*sizeof(unsigned int), "prv_rect"); + } +} + +/* create single icon from jpg, png etc. */ +static void icon_from_image(Image *img, int miplevel) +{ + unsigned int pr_size; + short image_loaded = 0; + struct ImBuf* ibuf=NULL; + PreviewImage* pi; + + /* img->ok is zero when Image cannot load */ + if (img==NULL || img->ok==0) + return; + + /* elubie: this needs to be changed: here image is always loaded if not + already there. Very expensive for large images. Need to find a way to + only get existing ibuf */ + ibuf = BKE_image_get_ibuf(img, NULL); + if(ibuf==NULL || ibuf->rect==NULL) { + return; + } + + pi = BKE_previewimg_get((ID*)img); + + if(!pi) { + printf("preview image could'nt be allocated"); + return; + } + /* we can only create the preview rect here, since loading possibly deallocated + old preview */ + icon_create_mipmap(pi, miplevel); + + pr_size = img->preview->w[miplevel]*img->preview->h[miplevel]*sizeof(unsigned int); + + image_loaded = 1; + icon_copy_rect(ibuf, img->preview->w[miplevel], img->preview->h[miplevel], img->preview->rect[miplevel]); +} + +static void set_alpha(char* cp, int sizex, int sizey, char alpha) +{ + int x,y; + for(y=0; y<sizey; y++) { + for(x=0; x<sizex; x++, cp+=4) { + cp[3]= alpha; + } + } +} +#endif + +/* only called when icon has changed */ +/* only call with valid pointer from UI_icon_draw */ +static void icon_set_image(ID *id, DrawInfo *di, PreviewImage* prv_img, int miplevel) +{ +#if 0 // XXX - preview renders have to be redesigned - possibly low level op (elubie) + RenderInfo ri; + unsigned int pr_size = 0; + + if (!di) return; + + if (!prv_img) { + printf("No preview image for this ID: %s\n", id->name); + return; + } + + /* no drawing (see last parameter doDraw, just calculate preview image + - hopefully small enough to be fast */ + if (GS(id->name) == ID_IM) + icon_from_image((struct Image*)id, miplevel); + else { + /* create the preview rect */ + icon_create_mipmap(prv_img, miplevel); + + ri.curtile= 0; + ri.tottile= 0; + ri.rect = NULL; + ri.pr_rectx = prv_img->w[miplevel]; + ri.pr_recty = prv_img->h[miplevel]; + + pr_size = ri.pr_rectx*ri.pr_recty*sizeof(unsigned int); + + BIF_previewrender(id, &ri, NULL, PR_ICON_RENDER); + + /* world is rendered with alpha=0, so it wasn't displayed + this could be render option for sky to, for later */ + if (GS(id->name) == ID_WO) { + set_alpha( (char*) ri.rect, ri.pr_rectx, ri.pr_recty, 255); + } + else if (GS(id->name) == ID_MA) { + Material* mat = (Material*)id; + if (mat->material_type == MA_TYPE_HALO) { + set_alpha( (char*) ri.rect, ri.pr_rectx, ri.pr_recty, 255); + } + } + + if (ri.rect) { + memcpy(prv_img->rect[miplevel], ri.rect, pr_size); + + /* and clean up */ + MEM_freeN(ri.rect); + ri.rect = 0; + } + } +#endif +} + +static void icon_draw_rect(float x, float y, int w, int h, float aspect, int rw, int rh, unsigned int *rect) +{ + + glRasterPos2f(x, y); + // XXX ui_rasterpos_safe(x, y, aspect); + + if((w<1 || h<1)) { + // XXX - TODO 2.5 verify whether this case can happen + // and only print in debug + printf("what the heck! - icons are %i x %i pixels?\n", w, h); + } + /* rect contains image in 'rendersize', we only scale if needed */ + else if(rw!=w && rh!=h) { + ImBuf *ima; + if(w>2000 || h>2000) { /* something has gone wrong! */ + printf("insane icon size w=%d h=%d\n",w,h); + return; + } + /* first allocate imbuf for scaling and copy preview into it */ + ima = IMB_allocImBuf(rw, rh, 32, IB_rect, 0); + memcpy(ima->rect, rect, rw*rh*sizeof(unsigned int)); + + /* scale it */ + IMB_scaleImBuf(ima, w, h); + glDrawPixels(w, h, GL_RGBA, GL_UNSIGNED_BYTE, ima->rect); + + IMB_freeImBuf(ima); + } + else + glDrawPixels(w, h, GL_RGBA, GL_UNSIGNED_BYTE, rect); +} + +/* Drawing size for preview images at level miplevel */ +static int preview_size(int miplevel) +{ + switch (miplevel) { + case 0: return ICON_DEFAULT_HEIGHT; + case 1: return PREVIEW_DEFAULT_HEIGHT; + } + return 0; +} + +static void icon_draw_size(float x, float y, int icon_id, float aspect, int miplevel, int draw_size, int nocreate) +{ + Icon *icon = NULL; + DrawInfo *di = NULL; + + icon = BKE_icon_get(icon_id); + + if (!icon) { + printf("icon_draw_mipmap: Internal error, no icon for icon ID: %d\n", icon_id); + return; + } + + di = (DrawInfo*)icon->drawinfo; + + if (!di) { + di = icon_create_drawinfo(); + + icon->drawinfo = di; + icon->drawinfo_free = UI_icons_free_drawinfo; + } + + di->aspect = aspect; + /* scale width and height according to aspect */ + di->w = (int)(draw_size/di->aspect + 0.5f); + di->h = (int)(draw_size/di->aspect + 0.5f); + + if (di->drawFunc) { + /* vector icons use the uiBlock transformation, they are not drawn + with untransformed coordinates like the other icons */ + di->drawFunc(x, y, ICON_DEFAULT_HEIGHT, ICON_DEFAULT_HEIGHT, 1.0f); + } + else if (di->icon) { + /* it is a builtin icon */ + if (!di->icon->rect) return; /* something has gone wrong! */ + + icon_draw_rect(x,y,di->w, di->h, di->aspect, di->icon->w, di->icon->h, di->icon->rect); + } + else { + PreviewImage* pi = BKE_previewimg_get((ID*)icon->obj); + + if (pi) { + if (!nocreate && (pi->changed[miplevel] ||!pi->rect[miplevel])) /* changed only ever set by dynamic icons */ + { + // XXX waitcursor(1); + /* create the preview rect if necessary */ + icon_set_image((ID*)icon->obj, icon->drawinfo, pi, miplevel); + pi->changed[miplevel] = 0; + // XXX waitcursor(0); + } + + if (!pi->rect[miplevel]) return; /* something has gone wrong! */ + + icon_draw_rect(x,y,di->w, di->h, di->aspect, pi->w[miplevel], pi->h[miplevel], pi->rect[miplevel]); + } + } +} + +static void icon_draw_mipmap(float x, float y, int icon_id, float aspect, int miplevel, int nocreate) +{ + int draw_size = preview_size(miplevel); + icon_draw_size(x,y,icon_id, aspect, miplevel, draw_size, nocreate); +} + + +void UI_icon_draw_aspect(float x, float y, int icon_id, float aspect) +{ + icon_draw_mipmap(x,y,icon_id, aspect, PREVIEW_MIPMAP_ZERO, 0); +} + +void UI_icon_draw(float x, float y, int icon_id) +{ + UI_icon_draw_aspect(x, y, icon_id, 1.0f); +} + +void UI_icon_draw_size_blended(float x, float y, int size, int icon_id, int shade) +{ + if(shade < 0) { + float r= (128+shade)/128.0f; + glPixelTransferf(GL_ALPHA_SCALE, r); + } + + icon_draw_size(x,y,icon_id, 1.0f, 0, size, 1); + + if(shade < 0) + glPixelTransferf(GL_ALPHA_SCALE, 1.0f); +} + +void UI_icon_draw_preview(float x, float y, int icon_id, int nocreate) +{ + icon_draw_mipmap(x,y,icon_id, 1.0f, PREVIEW_MIPMAP_LARGE, nocreate); +} + +void UI_icon_draw_aspect_blended(float x, float y, int icon_id, float aspect, int shade) +{ + if(shade < 0) { + float r= (128+shade)/128.0f; + glPixelTransferf(GL_ALPHA_SCALE, r); + } + + UI_icon_draw_aspect(x, y, icon_id, aspect); + + if(shade < 0) + glPixelTransferf(GL_ALPHA_SCALE, 1.0f); +} diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h new file mode 100644 index 00000000000..18423434eef --- /dev/null +++ b/source/blender/editors/interface/interface_intern.h @@ -0,0 +1,435 @@ +/** + * $Id: interface.h 14444 2008-04-16 22:40:48Z hos $ + * + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + * + * The Original Code is: all of this file. + * + * Contributor(s): none yet. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#ifndef INTERFACE_H +#define INTERFACE_H + +#include "UI_resources.h" +#include "RNA_types.h" + +struct ARegion; +struct bContext; +struct IDProperty; +struct uiHandleButtonData; +struct wmEvent; +struct wmOperatorType; +struct wmWindow; +struct uiStyle; +struct uiWidgetColors; +struct uiLayout; +struct bContextStore; + +/* ****************** general defines ************** */ + +/* visual types for drawing */ +/* for time being separated from functional types */ +typedef enum { + /* standard set */ + UI_WTYPE_LABEL, + UI_WTYPE_TOGGLE, + UI_WTYPE_OPTION, + UI_WTYPE_RADIO, + UI_WTYPE_NUMBER, + UI_WTYPE_SLIDER, + UI_WTYPE_EXEC, + + /* strings */ + UI_WTYPE_NAME, + UI_WTYPE_NAME_LINK, + UI_WTYPE_POINTER_LINK, + UI_WTYPE_FILENAME, + + /* menus */ + UI_WTYPE_MENU_RADIO, + UI_WTYPE_MENU_POINTER_LINK, + + UI_WTYPE_PULLDOWN, + UI_WTYPE_MENU_ITEM, + UI_WTYPE_MENU_BACK, + + /* specials */ + UI_WTYPE_ICON, + UI_WTYPE_SWATCH, + UI_WTYPE_RGB_PICKER, + UI_WTYPE_NORMAL, + UI_WTYPE_BOX + +} uiWidgetTypeEnum; + + + +#define UI_MAX_DRAW_STR 400 +#define UI_MAX_NAME_STR 64 +#define UI_ARRAY 29 + +/* panel limits */ +#define UI_PANEL_MINX 100 +#define UI_PANEL_MINY 70 + +/* uiBut->flag */ +#define UI_SELECT 1 +#define UI_MOUSE_OVER 2 +#define UI_ACTIVE 4 +#define UI_HAS_ICON 8 +#define UI_TEXTINPUT 16 + +/* warn: rest of uiBut->flag in UI_interface.h */ + +/* internal panel drawing defines */ +#define PNL_GRID 4 +#define PNL_HEADER 20 + +/* panel->flag */ +#define PNL_SELECT 1 +#define PNL_CLOSEDX 2 +#define PNL_CLOSEDY 4 +#define PNL_CLOSED 6 +#define PNL_TABBED 8 +#define PNL_OVERLAP 16 + +/* Button text selection: + * extension direction, selextend, inside ui_do_but_TEX */ +#define EXTEND_LEFT 1 +#define EXTEND_RIGHT 2 + +typedef struct { + short xim, yim; + unsigned int *rect; + short xofs, yofs; +} uiIconImage; + + +typedef struct uiLinkLine { /* only for draw/edit */ + struct uiLinkLine *next, *prev; + + short flag, pad; + + struct uiBut *from, *to; +} uiLinkLine; + +typedef struct { + void **poin; /* pointer to original pointer */ + void ***ppoin; /* pointer to original pointer-array */ + short *totlink; /* if pointer-array, here is the total */ + + short maxlink, pad; + short fromcode, tocode; + + ListBase lines; +} uiLink; + +struct uiBut { + struct uiBut *next, *prev; + short type, pointype, bit, bitnr, retval, strwidth, ofs, pos, selsta, selend; + short alignnr; + int flag; + + char *str; + char strdata[UI_MAX_NAME_STR]; + char drawstr[UI_MAX_DRAW_STR]; + + float x1, y1, x2, y2; + + char *poin; + float hardmin, hardmax, softmin, softmax; + float a1, a2, hsv[3]; // hsv is temp memory for hsv buttons + float aspect; + + uiButHandleFunc func; + void *func_arg1; + void *func_arg2; + void *func_arg3; + + uiButHandleNFunc funcN; + void *func_argN; + + struct bContextStore *context; + + void (*embossfunc)(int , int , float, float, float, float, float, int); + void (*sliderfunc)(int , float, float, float, float, float, float, int); + + uiButCompleteFunc autocomplete_func; + void *autofunc_arg; + + uiButSearchFunc search_func; + void *search_arg; + + uiLink *link; + + char *tip, *lockstr; + + BIFIconID icon; + short but_align; /* aligning buttons, horiz/vertical */ + short lock, win; + short iconadd, dt; + + /* IDPOIN data */ + uiIDPoinFuncFP idpoin_func; + ID **idpoin_idpp; + + /* BLOCK data */ + uiBlockCreateFunc block_create_func; + + /* PULLDOWN/MENU data */ + uiMenuCreateFunc menu_create_func; + + /* RNA data */ + struct PointerRNA rnapoin; + struct PropertyRNA *rnaprop; + int rnaindex; + + /* Operator data */ + struct wmOperatorType *optype; + int opcontext; + struct IDProperty *opproperties; + struct PointerRNA *opptr; + + /* active button data */ + struct uiHandleButtonData *active; + + char *editstr; + double *editval; + float *editvec; + void *editcoba; + void *editcumap; + + /* pointer back */ + uiBlock *block; +}; + +struct uiBlock { + uiBlock *next, *prev; + + ListBase buttons; + Panel *panel; + uiBlock *oldblock; + + ListBase layouts; + struct uiLayout *curlayout; + + ListBase contexts; + + char name[UI_MAX_NAME_STR]; + + float winmat[4][4]; + + float minx, miny, maxx, maxy; + float aspect; + + short alignnr; + + uiButHandleFunc func; + void *func_arg1; + void *func_arg2; + + uiMenuHandleFunc butm_func; + void *butm_func_arg; + + uiBlockHandleFunc handle_func; + void *handle_func_arg; + + /* extra draw function for custom blocks */ + void (*drawextra)(const struct bContext *C, void *idv, rcti *rect); + + int afterval, flag; + + short direction, dt; + short auto_open, in_use; + double auto_open_last; + + int lock; + char *lockstr; + + float xofs, yofs; // offset to parent button + int bounds, dobounds, mx, my; // for doing delayed + int endblock; // uiEndBlock done? + + rctf safety; // pulldowns, to detect outside, can differ per case how it is created + ListBase saferct; // uiSafetyRct list + + uiPopupBlockHandle *handle; // handle + int tooltipdisabled; // to avoid tooltip after click + + int active; // to keep blocks while drawing and free them afterwards +}; + +typedef struct uiSafetyRct { + struct uiSafetyRct *next, *prev; + rctf parent; + rctf safety; +} uiSafetyRct; + +/* interface.c */ + +extern int ui_translate_buttons(void); +extern int ui_translate_menus(void); +extern int ui_translate_tooltips(void); + +void ui_fontscale(short *points, float aspect); + +extern void ui_block_to_window_fl(const struct ARegion *ar, uiBlock *block, float *x, float *y); +extern void ui_block_to_window(const struct ARegion *ar, uiBlock *block, int *x, int *y); +extern void ui_block_to_window_rct(const struct ARegion *ar, uiBlock *block, rctf *graph, rcti *winr); +extern void ui_window_to_block_fl(const struct ARegion *ar, uiBlock *block, float *x, float *y); +extern void ui_window_to_block(const struct ARegion *ar, uiBlock *block, int *x, int *y); +extern void ui_window_to_region(const ARegion *ar, int *x, int *y); + +extern double ui_get_but_val(uiBut *but); +extern void ui_set_but_val(uiBut *but, double value); +extern void ui_set_but_hsv(uiBut *but); +extern void ui_get_but_vectorf(uiBut *but, float *vec); +extern void ui_set_but_vectorf(uiBut *but, float *vec); + +extern void ui_get_but_string(uiBut *but, char *str, int maxlen); +extern int ui_set_but_string(struct bContext *C, uiBut *but, const char *str); +extern int ui_get_but_string_max_length(uiBut *but); + +extern void ui_set_but_soft_range(uiBut *but, double value); + +extern void ui_check_but(uiBut *but); +extern int ui_is_but_float(uiBut *but); +extern void ui_update_block_buts_hsv(uiBlock *block, float *hsv); + +extern void ui_bounds_block(uiBlock *block); +extern void ui_block_translate(uiBlock *block, int x, int y); +extern void ui_block_do_align(uiBlock *block); + +/* interface_regions.c */ + +struct uiPopupBlockHandle { + /* internal */ + struct ARegion *region; + int towardsx, towardsy; + double towardstime; + int dotowards; + + int popup; + void (*popup_func)(struct bContext *C, void *arg, int event); + void (*cancel_func)(void *arg); + void *popup_arg; + + /* for operator popups */ + struct wmOperatorType *optype; + int opcontext; + ScrArea *ctx_area; + ARegion *ctx_region; + + /* return values */ + int butretval; + int menuretval; + float retvalue; + float retvec[3]; +}; + +uiBlock *ui_block_func_MENU(struct bContext *C, uiPopupBlockHandle *handle, void *arg_but); +uiBlock *ui_block_func_ICONROW(struct bContext *C, uiPopupBlockHandle *handle, void *arg_but); +uiBlock *ui_block_func_ICONTEXTROW(struct bContext *C, uiPopupBlockHandle *handle, void *arg_but); +uiBlock *ui_block_func_COL(struct bContext *C, uiPopupBlockHandle *handle, void *arg_but); + +struct ARegion *ui_tooltip_create(struct bContext *C, struct ARegion *butregion, uiBut *but); +void ui_tooltip_free(struct bContext *C, struct ARegion *ar); + +/* searchbox for string button */ +ARegion *ui_searchbox_create(struct bContext *C, struct ARegion *butregion, uiBut *but); +void ui_searchbox_update(struct bContext *C, struct ARegion *ar, uiBut *but, int reset); +void ui_searchbox_event(struct bContext *C, struct ARegion *ar, uiBut *but, struct wmEvent *event); +void ui_searchbox_apply(uiBut *but, struct ARegion *ar); +void ui_searchbox_free(struct bContext *C, struct ARegion *ar); + +typedef uiBlock* (*uiBlockHandleCreateFunc)(struct bContext *C, struct uiPopupBlockHandle *handle, void *arg1); + +uiPopupBlockHandle *ui_popup_block_create(struct bContext *C, struct ARegion *butregion, uiBut *but, + uiBlockCreateFunc create_func, uiBlockHandleCreateFunc handle_create_func, void *arg); +uiPopupBlockHandle *ui_popup_menu_create(struct bContext *C, struct ARegion *butregion, uiBut *but, + uiMenuCreateFunc create_func, void *arg); +void ui_popup_block_free(struct bContext *C, uiPopupBlockHandle *handle); + +void ui_set_name_menu(uiBut *but, int value); +int ui_step_name_menu(uiBut *but, int step); + +struct AutoComplete; +struct AutoComplete *autocomplete_begin(char *startname, int maxlen); +void autocomplete_do_name(struct AutoComplete *autocpl, const char *name); +void autocomplete_end(struct AutoComplete *autocpl, char *autoname); + +/* interface_panel.c */ +extern int ui_handler_panel_region(struct bContext *C, struct wmEvent *event); +extern void ui_draw_aligned_panel(struct ARegion *ar, struct uiStyle *style, uiBlock *block, rcti *rect); + +/* interface_draw.c */ +extern void ui_dropshadow(rctf *rct, float radius, float aspect, int select); + +extern void gl_round_box(int mode, float minx, float miny, float maxx, float maxy, float rad); +extern void gl_round_box_shade(int mode, float minx, float miny, float maxx, float maxy, float rad, float shadetop, float shadedown); +extern void gl_round_box_vertical_shade(int mode, float minx, float miny, float maxx, float maxy, float rad, float shadeLeft, float shadeRight); + +void ui_draw_but_COLORBAND(uiBut *but, struct uiWidgetColors *wcol, rcti *rect); +void ui_draw_but_NORMAL(uiBut *but, struct uiWidgetColors *wcol, rcti *rect); +void ui_draw_but_CURVE(ARegion *ar, uiBut *but, struct uiWidgetColors *wcol, rcti *rect); + + +/* interface_handlers.c */ +extern void ui_button_active_cancel(const struct bContext *C, uiBut *but); +extern int ui_button_is_active(struct ARegion *ar); + +/* interface_widgets.c */ +void ui_draw_anti_tria(float x1, float y1, float x2, float y2, float x3, float y3); +void ui_draw_menu_back(struct uiStyle *style, uiBlock *block, rcti *rect); +void ui_draw_search_back(struct uiStyle *style, uiBlock *block, rcti *rect); + +extern void ui_draw_but(const struct bContext *C, ARegion *ar, struct uiStyle *style, uiBut *but, rcti *rect); + /* theme color init */ +struct ThemeUI; +void ui_widget_color_init(struct ThemeUI *tui); + +void ui_draw_menu_item(struct uiFontStyle *fstyle, rcti *rect, char *name, int state); + +/* interface_style.c */ +void uiStyleInit(void); + +/* resources.c */ +void init_userdef_do_versions(void); +void ui_theme_init_userdef(void); +void ui_resources_init(void); +void ui_resources_free(void); + +/* interface_layout.c */ +void ui_layout_add_but(struct uiLayout *layout, uiBut *but); +int ui_but_can_align(uiBut *but); + +/* interface_anim.c */ +void ui_but_anim_flag(uiBut *but, float cfra); +void ui_but_anim_insert_keyframe(struct bContext *C); +void ui_but_anim_delete_keyframe(struct bContext *C); +void ui_but_anim_add_driver(struct bContext *C); +void ui_but_anim_remove_driver(struct bContext *C); +void ui_but_anim_menu(struct bContext *C, uiBut *but); + +#endif + diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c new file mode 100644 index 00000000000..e112ca704bb --- /dev/null +++ b/source/blender/editors/interface/interface_layout.c @@ -0,0 +1,2013 @@ +/** + * $Id$ + * + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contributor(s): Blender Foundation 2009. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include <limits.h> +#include <math.h> +#include <stdlib.h> +#include <string.h> + +#include "MEM_guardedalloc.h" + +#include "DNA_ID.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" +#include "DNA_userdef_types.h" +#include "DNA_windowmanager_types.h" + +#include "BLI_listbase.h" +#include "BLI_string.h" + +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_idprop.h" +#include "BKE_library.h" +#include "BKE_screen.h" +#include "BKE_utildefines.h" + +#include "RNA_access.h" + +#include "UI_interface.h" +#include "UI_resources.h" +#include "UI_view2d.h" + +#include "ED_util.h" +#include "ED_types.h" +#include "ED_screen.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "interface_intern.h" + +/************************ Structs and Defines *************************/ + +#define RNA_NO_INDEX -1 +#define RNA_ENUM_VALUE -2 + +#define EM_SEPR_X 6 +#define EM_SEPR_Y 6 + +/* uiLayoutRoot */ + +typedef struct uiLayoutRoot { + struct uiLayoutRoot *next, *prev; + + int type; + int opcontext; + + int emw, emh; + + uiMenuHandleFunc handlefunc; + void *argv; + + uiStyle *style; + uiBlock *block; + uiLayout *layout; +} uiLayoutRoot; + +/* Item */ + +typedef enum uiItemType { + ITEM_BUTTON, + + ITEM_LAYOUT_ROW, + ITEM_LAYOUT_COLUMN, + ITEM_LAYOUT_COLUMN_FLOW, + ITEM_LAYOUT_ROW_FLOW, + ITEM_LAYOUT_BOX, + ITEM_LAYOUT_FREE, + ITEM_LAYOUT_SPLIT, + + ITEM_LAYOUT_ROOT +#if 0 + TEMPLATE_COLUMN_FLOW, + TEMPLATE_SPLIT, + TEMPLATE_BOX, + + TEMPLATE_HEADER, + TEMPLATE_HEADER_ID +#endif +} uiItemType; + +typedef struct uiItem { + void *next, *prev; + uiItemType type; + int flag; +} uiItem; + +typedef struct uiButtonItem { + uiItem item; + uiBut *but; +} uiButtonItem; + +struct uiLayout { + uiItem item; + + uiLayoutRoot *root; + bContextStore *context; + ListBase items; + + int x, y, w, h; + float scale[2]; + short space; + char align; + char active; + char enabled; + char redalert; + char keepaspect; + char alignment; +}; + +typedef struct uiLayoutItemFlow { + uiLayout litem; + int number; + int totcol; +} uiLayoutItemFlow; + +typedef struct uiLayoutItemBx { + uiLayout litem; + uiBut *roundbox; +} uiLayoutItemBx; + +typedef struct uiLayoutItemSplt { + uiLayout litem; + float percentage; +} uiLayoutItemSplt; + +typedef struct uiLayoutItemRoot { + uiLayout litem; +} uiLayoutItemRoot; + +/************************** Item ***************************/ + +static char *ui_item_name_add_colon(char *name, char namestr[UI_MAX_NAME_STR]) +{ + int len= strlen(name); + + if(len != 0 && len+1 < UI_MAX_NAME_STR) { + BLI_strncpy(namestr, name, UI_MAX_NAME_STR); + namestr[len]= ':'; + namestr[len+1]= '\0'; + return namestr; + } + + return name; +} + +static int ui_item_fit(int item, int pos, int all, int available, int last, int alignment, int *offset) +{ + /* available == 0 is unlimited */ + if(available == 0) + return item; + + if(offset) + *offset= 0; + + if(all > available) { + /* contents is bigger than available space */ + if(last) + return available-pos; + else + return (item*available)/all; + } + else { + /* contents is smaller or equal to available space */ + if(alignment == UI_LAYOUT_ALIGN_EXPAND) { + if(last) + return available-pos; + else + return (item*available)/all; + } + else + return item; + } +} + +/* variable button size in which direction? */ +#define UI_ITEM_VARY_X 1 +#define UI_ITEM_VARY_Y 2 + +static int ui_layout_vary_direction(uiLayout *layout) +{ + return (layout->root->type == UI_LAYOUT_HEADER || layout->alignment != UI_LAYOUT_ALIGN_EXPAND)? UI_ITEM_VARY_X: UI_ITEM_VARY_Y; +} + +/* estimated size of text + icon */ +static int ui_text_icon_width(uiLayout *layout, char *name, int icon) +{ + int variable = ui_layout_vary_direction(layout) == UI_ITEM_VARY_X; + + if(icon && strcmp(name, "") == 0) + return UI_UNIT_X; /* icon only */ + else if(icon) + return (variable)? UI_GetStringWidth(name) + 4 + UI_UNIT_X: 10*UI_UNIT_X; /* icon + text */ + else + return (variable)? UI_GetStringWidth(name) + 4 + UI_UNIT_X: 10*UI_UNIT_X; /* text only */ +} + +static void ui_item_size(uiItem *item, int *r_w, int *r_h) +{ + if(item->type == ITEM_BUTTON) { + uiButtonItem *bitem= (uiButtonItem*)item; + + if(r_w) *r_w= bitem->but->x2 - bitem->but->x1; + if(r_h) *r_h= bitem->but->y2 - bitem->but->y1; + } + else { + uiLayout *litem= (uiLayout*)item; + + if(r_w) *r_w= litem->w; + if(r_h) *r_h= litem->h; + } +} + +static void ui_item_offset(uiItem *item, int *r_x, int *r_y) +{ + if(item->type == ITEM_BUTTON) { + uiButtonItem *bitem= (uiButtonItem*)item; + + if(r_x) *r_x= bitem->but->x1; + if(r_y) *r_y= bitem->but->y1; + } + else { + if(r_x) *r_x= 0; + if(r_y) *r_y= 0; + } +} + +static void ui_item_position(uiItem *item, int x, int y, int w, int h) +{ + if(item->type == ITEM_BUTTON) { + uiButtonItem *bitem= (uiButtonItem*)item; + + bitem->but->x1= x; + bitem->but->y1= y; + bitem->but->x2= x+w; + bitem->but->y2= y+h; + + ui_check_but(bitem->but); /* for strlen */ + } + else { + uiLayout *litem= (uiLayout*)item; + + litem->x= x; + litem->y= y+h; + litem->w= w; + litem->h= h; + } +} + +/******************** Special RNA Items *********************/ + +static int ui_layout_local_dir(uiLayout *layout) +{ + switch(layout->item.type) { + case ITEM_LAYOUT_ROW: + case ITEM_LAYOUT_ROOT: + return UI_LAYOUT_HORIZONTAL; + case ITEM_LAYOUT_COLUMN: + case ITEM_LAYOUT_COLUMN_FLOW: + case ITEM_LAYOUT_SPLIT: + case ITEM_LAYOUT_FREE: + case ITEM_LAYOUT_BOX: + default: + return UI_LAYOUT_VERTICAL; + } +} + +static uiLayout *ui_item_local_sublayout(uiLayout *test, uiLayout *layout, int align) +{ + uiLayout *sub; + + if(ui_layout_local_dir(test) == UI_LAYOUT_HORIZONTAL) + sub= uiLayoutRow(layout, align); + else + sub= uiLayoutColumn(layout, align); + + sub->space= 0; + return sub; +} + +/* create buttons for an item with an RNA array */ +static void ui_item_array(uiLayout *layout, uiBlock *block, char *name, int icon, PointerRNA *ptr, PropertyRNA *prop, int len, int x, int y, int w, int h, int expand, int slider) +{ + uiStyle *style= layout->root->style; + uiBut *but; + PropertyType type; + PropertySubType subtype; + uiLayout *sub; + int a; + + /* retrieve type and subtype */ + type= RNA_property_type(prop); + subtype= RNA_property_subtype(prop); + + sub= ui_item_local_sublayout(layout, layout, 1); + uiBlockSetCurLayout(block, sub); + + /* create label */ + if(strcmp(name, "") != 0) + uiDefBut(block, LABEL, 0, name, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, ""); + + /* create buttons */ + if(type == PROP_BOOLEAN && len == 20) { + /* special check for layer layout */ + int butw, buth, unit; + + uiBlockSetCurLayout(block, uiLayoutFree(layout, 0)); + + unit= UI_UNIT_X*0.75; + butw= unit; + buth= unit; + + uiBlockBeginAlign(block); + for(a=0; a<5; a++) + uiDefAutoButR(block, ptr, prop, a, "", ICON_BLANK1, x + butw*a, y+buth, butw, buth); + for(a=0; a<5; a++) + uiDefAutoButR(block, ptr, prop, a+10, "", ICON_BLANK1, x + butw*a, y, butw, buth); + uiBlockEndAlign(block); + + x += 5*butw + style->buttonspacex; + + uiBlockBeginAlign(block); + for(a=0; a<5; a++) + uiDefAutoButR(block, ptr, prop, a+5, "", ICON_BLANK1, x + butw*a, y+buth, butw, buth); + for(a=0; a<5; a++) + uiDefAutoButR(block, ptr, prop, a+15, "", ICON_BLANK1, x + butw*a, y, butw, buth); + uiBlockEndAlign(block); + } + else if(subtype == PROP_MATRIX) { + /* matrix layout */ + int row, col; + + uiBlockSetCurLayout(block, uiLayoutFree(layout, 1)); + + len= ceil(sqrt(len)); + + h /= len; + w /= len; + + // XXX test + for(a=0; a<len; a++) { + col= a%len; + row= a/len; + + but= uiDefAutoButR(block, ptr, prop, a, "", 0, x + w*col, y+(row-a-1)*UI_UNIT_Y, w, UI_UNIT_Y); + if(slider && but->type==NUM) + but->type= NUMSLI; + } + } + else if(len <= 4 && ELEM3(subtype, PROP_ROTATION, PROP_VECTOR, PROP_COLOR)) { + if(subtype == PROP_COLOR) + uiDefAutoButR(block, ptr, prop, -1, "", 0, 0, 0, w, UI_UNIT_Y); + + if(subtype != PROP_COLOR || expand) { + /* layout for known array subtypes */ + static char vectoritem[4]= {'X', 'Y', 'Z', 'W'}; + static char quatitem[4]= {'W', 'X', 'Y', 'Z'}; + static char coloritem[4]= {'R', 'G', 'B', 'A'}; + char str[3]; + + for(a=0; a<len; a++) { + if(len == 4 && subtype == PROP_ROTATION) + str[0]= quatitem[a]; + else if(subtype == PROP_VECTOR || subtype == PROP_ROTATION) + str[0]= vectoritem[a]; + else + str[0]= coloritem[a]; + + if(type == PROP_BOOLEAN) { + str[1]= '\0'; + } + else { + str[1]= ':'; + str[2]= '\0'; + } + + but= uiDefAutoButR(block, ptr, prop, a, str, 0, 0, 0, w, UI_UNIT_Y); + if(slider && but->type==NUM) + but->type= NUMSLI; + } + } + else if(subtype == PROP_COLOR && len == 4) { + but= uiDefAutoButR(block, ptr, prop, 3, "A:", 0, 0, 0, w, UI_UNIT_Y); + if(slider && but->type==NUM) + but->type= NUMSLI; + } + } + else { + for(a=0; a<len; a++) { + but= uiDefAutoButR(block, ptr, prop, a, "", 0, 0, 0, w, UI_UNIT_Y); + if(slider && but->type==NUM) + but->type= NUMSLI; + } + } + + uiBlockSetCurLayout(block, layout); +} + +static void ui_item_enum_row(uiLayout *layout, uiBlock *block, PointerRNA *ptr, PropertyRNA *prop, int x, int y, int w, int h) +{ + const EnumPropertyItem *item; + int a, totitem, itemw; + const char *propname; + + propname= RNA_property_identifier(prop); + RNA_property_enum_items(ptr, prop, &item, &totitem); + + uiBlockSetCurLayout(block, ui_item_local_sublayout(layout, layout, 1)); + for(a=0; a<totitem; a++) { + itemw= ui_text_icon_width(block->curlayout, (char*)item[a].name, 0); + uiDefButR(block, ROW, 0, NULL, 0, 0, itemw, h, ptr, propname, -1, 0, item[a].value, -1, -1, NULL); + } + uiBlockSetCurLayout(block, layout); +} + +/* create label + button for RNA property */ +static void ui_item_with_label(uiLayout *layout, uiBlock *block, char *name, int icon, PointerRNA *ptr, PropertyRNA *prop, int index, int x, int y, int w, int h) +{ + uiLayout *sub; + PropertySubType subtype; + + sub= uiLayoutRow(layout, 0); + uiBlockSetCurLayout(block, sub); + + if(strcmp(name, "") != 0) { + w= w/2; + uiDefBut(block, LABEL, 0, name, x, y, w, h, NULL, 0.0, 0.0, 0, 0, ""); + } + + subtype= RNA_property_subtype(prop); + + if(subtype == PROP_FILEPATH || subtype == PROP_DIRPATH) { + uiBlockSetCurLayout(block, uiLayoutRow(sub, 1)); + uiDefAutoButR(block, ptr, prop, index, "", icon, x, y, w-UI_UNIT_X, h); + uiDefIconBut(block, BUT, 0, ICON_FILESEL, x, y, UI_UNIT_X, h, NULL, 0.0f, 0.0f, 0.0f, 0.0f, "DUMMY file select button"); /* XXX */ + } + else + uiDefAutoButR(block, ptr, prop, index, "", icon, x, y, w, h); + + uiBlockSetCurLayout(block, layout); +} + +/********************* Button Items *************************/ + +/* disabled item */ +static void ui_item_disabled(uiLayout *layout, char *name) +{ + uiBlock *block= layout->root->block; + uiBut *but; + int w; + + uiBlockSetCurLayout(block, layout); + + if(!name) + name= ""; + + w= ui_text_icon_width(layout, name, 0); + + but= uiDefBut(block, LABEL, 0, (char*)name, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, ""); + but->flag |= UI_BUT_DISABLED; + but->lock = 1; + but->lockstr = ""; +} + +/* operator items */ +void uiItemFullO(uiLayout *layout, char *name, int icon, char *idname, IDProperty *properties, int context) +{ + uiBlock *block= layout->root->block; + wmOperatorType *ot= WM_operatortype_find(idname); + uiBut *but; + int w; + + if(!ot) { + ui_item_disabled(layout, idname); + return; + } + + if(!name) + name= ot->name; + if(layout->root->type == UI_LAYOUT_MENU && !icon) + icon= ICON_BLANK1; + + /* create button */ + uiBlockSetCurLayout(block, layout); + + w= ui_text_icon_width(layout, name, icon); + + if(icon && strcmp(name, "") != 0) + but= uiDefIconTextButO(block, BUT, ot->idname, context, icon, (char*)name, 0, 0, w, UI_UNIT_Y, NULL); + else if(icon) + but= uiDefIconButO(block, BUT, ot->idname, context, icon, 0, 0, w, UI_UNIT_Y, NULL); + else + but= uiDefButO(block, BUT, ot->idname, context, (char*)name, 0, 0, w, UI_UNIT_Y, NULL); + + /* assign properties */ + if(properties) { + PointerRNA *opptr= uiButGetOperatorPtrRNA(but); + opptr->data= properties; + } +} + +static char *ui_menu_enumpropname(char *opname, char *propname, int retval) +{ + wmOperatorType *ot= WM_operatortype_find(opname); + PointerRNA ptr; + PropertyRNA *prop; + + if(!ot || !ot->srna) + return ""; + + RNA_pointer_create(NULL, ot->srna, NULL, &ptr); + prop= RNA_struct_find_property(&ptr, propname); + + if(prop) { + const EnumPropertyItem *item; + int totitem, i; + + RNA_property_enum_items(&ptr, prop, &item, &totitem); + + for (i=0; i<totitem; i++) { + if(item[i].value==retval) + return (char*)item[i].name; + } + } + + return ""; +} + +void uiItemEnumO(uiLayout *layout, char *name, int icon, char *opname, char *propname, int value) +{ + PointerRNA ptr; + + WM_operator_properties_create(&ptr, opname); + RNA_enum_set(&ptr, propname, value); + + if(!name) + name= ui_menu_enumpropname(opname, propname, value); + + uiItemFullO(layout, name, icon, opname, ptr.data, layout->root->opcontext); +} + +void uiItemsEnumO(uiLayout *layout, char *opname, char *propname) +{ + wmOperatorType *ot= WM_operatortype_find(opname); + PointerRNA ptr; + PropertyRNA *prop; + + if(!ot || !ot->srna) { + ui_item_disabled(layout, opname); + return; + } + + RNA_pointer_create(NULL, ot->srna, NULL, &ptr); + prop= RNA_struct_find_property(&ptr, propname); + + if(prop && RNA_property_type(prop) == PROP_ENUM) { + const EnumPropertyItem *item; + int totitem, i; + + RNA_property_enum_items(&ptr, prop, &item, &totitem); + + for(i=0; i<totitem; i++) + uiItemEnumO(layout, NULL, 0, opname, propname, item[i].value); + } +} + +/* for use in cases where we have */ +void uiItemEnumO_string(uiLayout *layout, char *name, int icon, char *opname, char *propname, char *value_str) +{ + PointerRNA ptr; + + /* for getting the enum */ + PropertyRNA *prop; + const EnumPropertyItem *item; + int totitem; + int value; + + WM_operator_properties_create(&ptr, opname); + + /*RNA_enum_set(&ptr, propname, value);*/ + if((prop= RNA_struct_find_property(&ptr, propname))) { + RNA_property_enum_items(&ptr, prop, &item, &totitem); + if(RNA_enum_value_from_id(item, value_str, &value)==0) { + printf("uiItemEnumO_string: %s.%s, enum %s not found.\n", RNA_struct_identifier(ptr.type), propname, value_str); + return; + } + } + else { + printf("uiItemEnumO_string: %s.%s not found.\n", RNA_struct_identifier(ptr.type), propname); + return; + } + + /* same as uiItemEnumO */ + if(!name) + name= ui_menu_enumpropname(opname, propname, value); + + uiItemFullO(layout, name, icon, opname, ptr.data, layout->root->opcontext); +} + +void uiItemBooleanO(uiLayout *layout, char *name, int icon, char *opname, char *propname, int value) +{ + PointerRNA ptr; + + WM_operator_properties_create(&ptr, opname); + RNA_boolean_set(&ptr, propname, value); + + uiItemFullO(layout, name, icon, opname, ptr.data, layout->root->opcontext); +} + +void uiItemIntO(uiLayout *layout, char *name, int icon, char *opname, char *propname, int value) +{ + PointerRNA ptr; + + WM_operator_properties_create(&ptr, opname); + RNA_int_set(&ptr, propname, value); + + uiItemFullO(layout, name, icon, opname, ptr.data, layout->root->opcontext); +} + +void uiItemFloatO(uiLayout *layout, char *name, int icon, char *opname, char *propname, float value) +{ + PointerRNA ptr; + + WM_operator_properties_create(&ptr, opname); + RNA_float_set(&ptr, propname, value); + + uiItemFullO(layout, name, icon, opname, ptr.data, layout->root->opcontext); +} + +void uiItemStringO(uiLayout *layout, char *name, int icon, char *opname, char *propname, char *value) +{ + PointerRNA ptr; + + WM_operator_properties_create(&ptr, opname); + RNA_string_set(&ptr, propname, value); + + uiItemFullO(layout, name, icon, opname, ptr.data, layout->root->opcontext); +} + +void uiItemO(uiLayout *layout, char *name, int icon, char *opname) +{ + uiItemFullO(layout, name, icon, opname, NULL, layout->root->opcontext); +} + +/* RNA property items */ + +static void ui_item_rna_size(uiLayout *layout, char *name, int icon, PropertyRNA *prop, int index, int *r_w, int *r_h) +{ + PropertyType type; + PropertySubType subtype; + int len, w, h; + + w= ui_text_icon_width(layout, name, icon); + h= UI_UNIT_Y; + + /* arbitrary extended width by type */ + type= RNA_property_type(prop); + subtype= RNA_property_subtype(prop); + len= RNA_property_array_length(prop); + + /* increase height for arrays */ + if(index == RNA_NO_INDEX && len > 0) { + if(strcmp(name, "") == 0 && icon == 0) + h= 0; + + if(type == PROP_BOOLEAN && len == 20) + h += 2*UI_UNIT_Y; + else if(subtype == PROP_MATRIX) + h += ceil(sqrt(len))*UI_UNIT_Y; + else + h += len*UI_UNIT_Y; + } + else if(ui_layout_vary_direction(layout) == UI_ITEM_VARY_X) { + if(type == PROP_BOOLEAN && strcmp(name, "") != 0) + w += UI_UNIT_X; + } + + *r_w= w; + *r_h= h; +} + +void uiItemFullR(uiLayout *layout, char *name, int icon, PointerRNA *ptr, PropertyRNA *prop, int index, int value, int expand, int slider, int toggle) +{ + uiBlock *block= layout->root->block; + uiBut *but; + PropertyType type; + char namestr[UI_MAX_NAME_STR]; + int len, w, h; + + if(!ptr->data || !prop) + return; + + uiBlockSetCurLayout(block, layout); + + /* retrieve info */ + type= RNA_property_type(prop); + len= RNA_property_array_length(prop); + + /* set name and icon */ + if(!name) + name= (char*)RNA_property_ui_name(prop); + + if(ELEM5(type, PROP_INT, PROP_FLOAT, PROP_STRING, PROP_ENUM, PROP_POINTER)) + name= ui_item_name_add_colon(name, namestr); + if(type == PROP_BOOLEAN && len) + name= ui_item_name_add_colon(name, namestr); + + if(layout->root->type == UI_LAYOUT_MENU) { + if(type == PROP_BOOLEAN) + icon= (RNA_property_boolean_get(ptr, prop))? ICON_CHECKBOX_HLT: ICON_CHECKBOX_DEHLT; + else if(type == PROP_ENUM && index == RNA_ENUM_VALUE) + icon= (RNA_property_enum_get(ptr, prop) == value)? ICON_CHECKBOX_HLT: ICON_CHECKBOX_DEHLT; + } + + /* get size */ + ui_item_rna_size(layout, name, icon, prop, index, &w, &h); + + /* array property */ + if(index == RNA_NO_INDEX && len > 0) + ui_item_array(layout, block, name, icon, ptr, prop, len, 0, 0, w, h, expand, slider); + /* enum item */ + else if(type == PROP_ENUM && index == RNA_ENUM_VALUE) { + char *identifier= (char*)RNA_property_identifier(prop); + + if(icon && strcmp(name, "") != 0) + uiDefIconTextButR(block, ROW, 0, icon, name, 0, 0, w, h, ptr, identifier, -1, 0, value, -1, -1, NULL); + else if(icon) + uiDefIconButR(block, ROW, 0, icon, 0, 0, w, h, ptr, identifier, -1, 0, value, -1, -1, NULL); + else + uiDefButR(block, ROW, 0, name, 0, 0, w, h, ptr, identifier, -1, 0, value, -1, -1, NULL); + } + /* expanded enum */ + else if(type == PROP_ENUM && expand) + ui_item_enum_row(layout, block, ptr, prop, 0, 0, w, h); + /* property with separate label */ + else if(type == PROP_ENUM || type == PROP_STRING || type == PROP_POINTER) + ui_item_with_label(layout, block, name, icon, ptr, prop, index, 0, 0, w, h); + /* single button */ + else { + but= uiDefAutoButR(block, ptr, prop, index, (char*)name, icon, 0, 0, w, h); + + if(slider && but->type==NUM) + but->type= NUMSLI; + + if(toggle && but->type==OPTION) + but->type= TOG; + } +} + +void uiItemR(uiLayout *layout, char *name, int icon, PointerRNA *ptr, char *propname, int expand, int slider, int toggle) +{ + PropertyRNA *prop; + + if(!ptr->data || !propname) + return; + + prop= RNA_struct_find_property(ptr, propname); + + if(!prop) { + ui_item_disabled(layout, propname); + printf("uiItemR: property not found: %s\n", propname); + return; + } + + uiItemFullR(layout, name, icon, ptr, prop, RNA_NO_INDEX, 0, expand, slider, toggle); +} + +void uiItemEnumR(uiLayout *layout, char *name, int icon, struct PointerRNA *ptr, char *propname, int value) +{ + PropertyRNA *prop; + + if(!ptr->data || !propname) + return; + + prop= RNA_struct_find_property(ptr, propname); + + if(!prop) { + ui_item_disabled(layout, propname); + printf("uiItemEnumR: property not found: %s\n", propname); + return; + } + + uiItemFullR(layout, name, icon, ptr, prop, RNA_ENUM_VALUE, value, 0, 0, 0); +} + +void uiItemsEnumR(uiLayout *layout, struct PointerRNA *ptr, char *propname) +{ + PropertyRNA *prop; + + prop= RNA_struct_find_property(ptr, propname); + + if(!prop) { + ui_item_disabled(layout, propname); + return; + } + + if(RNA_property_type(prop) == PROP_ENUM) { + const EnumPropertyItem *item; + int totitem, i; + + RNA_property_enum_items(ptr, prop, &item, &totitem); + + for(i=0; i<totitem; i++) + uiItemEnumR(layout, (char*)item[i].name, 0, ptr, propname, item[i].value); + } +} + +/* menu item */ +static void ui_item_menutype_func(bContext *C, uiLayout *layout, void *arg_mt) +{ + MenuType *mt= (MenuType*)arg_mt; + Menu menu = {0}; + + menu.type= mt; + menu.layout= layout; + mt->draw(C, &menu); +} + +static void ui_item_menu(uiLayout *layout, char *name, int icon, uiMenuCreateFunc func, void *arg, void *argN) +{ + uiBlock *block= layout->root->block; + uiBut *but; + int w, h; + + uiBlockSetCurLayout(block, layout); + + if(layout->root->type == UI_LAYOUT_HEADER) + uiBlockSetEmboss(block, UI_EMBOSSP); + + if(!name) + name= ""; + if(layout->root->type == UI_LAYOUT_MENU && !icon) + icon= ICON_BLANK1; + + w= ui_text_icon_width(layout, name, icon); + h= UI_UNIT_Y; + + if(layout->root->type == UI_LAYOUT_HEADER) /* ugly .. */ + w -= 3; + + if(icon) + but= uiDefIconTextMenuBut(block, func, arg, icon, (char*)name, 0, 0, w, h, ""); + else + but= uiDefMenuBut(block, func, arg, (char*)name, 0, 0, w, h, ""); + + if(argN) { /* ugly .. */ + but->poin= (char*)but; + but->func_argN= argN; + } + + if(layout->root->type == UI_LAYOUT_HEADER) + uiBlockSetEmboss(block, UI_EMBOSS); + else if(layout->root->type == UI_LAYOUT_PANEL) + but->type= MENU; +} + +void uiItemM(uiLayout *layout, bContext *C, char *name, int icon, char *menuname) +{ + ARegion *ar= CTX_wm_region(C); + MenuType *mt; + + if(!menuname) + return; + + for(mt=ar->type->menutypes.first; mt; mt=mt->next) { + if(strcmp(menuname, mt->idname) == 0) { + if(!name) + name= mt->label; + if(layout->root->type == UI_LAYOUT_MENU && !icon) + icon= ICON_BLANK1; + ui_item_menu(layout, name, icon, ui_item_menutype_func, mt, NULL); + break; + } + } +} + +/* label item */ +void uiItemL(uiLayout *layout, char *name, int icon) +{ + uiBlock *block= layout->root->block; + uiBut *but; + int w; + + uiBlockSetCurLayout(block, layout); + + if(!name) + name= ""; + if(layout->root->type == UI_LAYOUT_MENU && !icon) + icon= ICON_BLANK1; + + w= ui_text_icon_width(layout, name, icon); + + if(icon && strcmp(name, "") != 0) + but= uiDefIconTextBut(block, LABEL, 0, icon, (char*)name, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, ""); + else if(icon) + but= uiDefIconBut(block, LABEL, 0, icon, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, ""); + else + but= uiDefBut(block, LABEL, 0, (char*)name, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, ""); +} + +/* value item */ +void uiItemV(uiLayout *layout, char *name, int icon, int argval) +{ + /* label */ + uiBlock *block= layout->root->block; + float *retvalue= (block->handle)? &block->handle->retvalue: NULL; + int w; + + uiBlockSetCurLayout(block, layout); + + if(!name) + name= ""; + if(layout->root->type == UI_LAYOUT_MENU && !icon) + icon= ICON_BLANK1; + + w= ui_text_icon_width(layout, name, icon); + + if(icon && strcmp(name, "") != 0) + uiDefIconTextButF(block, BUTM, 0, icon, (char*)name, 0, 0, w, UI_UNIT_Y, retvalue, 0.0, 0.0, 0, argval, ""); + else if(icon) + uiDefIconButF(block, BUTM, 0, icon, 0, 0, w, UI_UNIT_Y, retvalue, 0.0, 0.0, 0, argval, ""); + else + uiDefButF(block, BUTM, 0, (char*)name, 0, 0, w, UI_UNIT_Y, retvalue, 0.0, 0.0, 0, argval, ""); +} + +/* separator item */ +void uiItemS(uiLayout *layout) +{ + uiBlock *block= layout->root->block; + + uiBlockSetCurLayout(block, layout); + uiDefBut(block, SEPR, 0, "", 0, 0, EM_SEPR_X, EM_SEPR_Y, NULL, 0.0, 0.0, 0, 0, ""); +} + +/* level items */ +void uiItemMenuF(uiLayout *layout, char *name, int icon, uiMenuCreateFunc func) +{ + if(!func) + return; + + ui_item_menu(layout, name, icon, func, NULL, NULL); +} + +typedef struct MenuItemLevel { + int opcontext; + char *opname; + char *propname; + PointerRNA rnapoin; +} MenuItemLevel; + +static void menu_item_enum_opname_menu(bContext *C, uiLayout *layout, void *arg) +{ + MenuItemLevel *lvl= (MenuItemLevel*)(((uiBut*)arg)->func_argN); + + uiLayoutSetOperatorContext(layout, WM_OP_EXEC_REGION_WIN); + uiItemsEnumO(layout, lvl->opname, lvl->propname); +} + +void uiItemMenuEnumO(uiLayout *layout, char *name, int icon, char *opname, char *propname) +{ + wmOperatorType *ot= WM_operatortype_find(opname); + MenuItemLevel *lvl; + + if(!ot || !ot->srna) { + ui_item_disabled(layout, opname); + return; + } + + if(!name) + name= ot->name; + if(layout->root->type == UI_LAYOUT_MENU && !icon) + icon= ICON_BLANK1; + + lvl= MEM_callocN(sizeof(MenuItemLevel), "MenuItemLevel"); + lvl->opname= opname; + lvl->propname= propname; + lvl->opcontext= layout->root->opcontext; + + ui_item_menu(layout, name, icon, menu_item_enum_opname_menu, NULL, lvl); +} + +static void menu_item_enum_rna_menu(bContext *C, uiLayout *layout, void *arg) +{ + MenuItemLevel *lvl= (MenuItemLevel*)(((uiBut*)arg)->func_argN); + + uiLayoutSetOperatorContext(layout, lvl->opcontext); + uiItemsEnumR(layout, &lvl->rnapoin, lvl->propname); +} + +void uiItemMenuEnumR(uiLayout *layout, char *name, int icon, struct PointerRNA *ptr, char *propname) +{ + MenuItemLevel *lvl; + PropertyRNA *prop; + + prop= RNA_struct_find_property(ptr, propname); + if(!prop) { + ui_item_disabled(layout, propname); + return; + } + + if(!name) + name= (char*)RNA_property_ui_name(prop); + if(layout->root->type == UI_LAYOUT_MENU && !icon) + icon= ICON_BLANK1; + + lvl= MEM_callocN(sizeof(MenuItemLevel), "MenuItemLevel"); + lvl->rnapoin= *ptr; + lvl->propname= propname; + lvl->opcontext= layout->root->opcontext; + + ui_item_menu(layout, name, icon, menu_item_enum_rna_menu, NULL, lvl); +} + +/**************************** Layout Items ***************************/ + +/* single-row layout */ +static void ui_litem_estimate_row(uiLayout *litem) +{ + uiItem *item; + int itemw, itemh; + + litem->w= 0; + litem->h= 0; + + for(item=litem->items.first; item; item=item->next) { + ui_item_size(item, &itemw, &itemh); + + litem->w += itemw; + litem->h= MAX2(itemh, litem->h); + + if(item->next) + litem->w += litem->space; + } +} + +static int ui_litem_min_width(int itemw) +{ + return MIN2(UI_UNIT_X, itemw); +} + +static void ui_litem_layout_row(uiLayout *litem) +{ + uiItem *item; + int x, y, w, tot, totw, neww, itemw, minw, itemh, offset; + int fixedw, freew, fixedx, freex, flag= 0, lastw= 0; + + x= litem->x; + y= litem->y; + w= litem->w; + totw= 0; + tot= 0; + + for(item=litem->items.first; item; item=item->next) { + ui_item_size(item, &itemw, &itemh); + totw += itemw; + tot++; + } + + if(totw == 0) + return; + + if(w != 0) + w -= (tot-1)*litem->space; + fixedw= 0; + + /* keep clamping items to fixed minimum size until all are done */ + do { + freew= 0; + x= 0; + flag= 0; + + for(item=litem->items.first; item; item=item->next) { + if(item->flag) + continue; + + ui_item_size(item, &itemw, &itemh); + minw= ui_litem_min_width(itemw); + + if(w - lastw > 0) + neww= ui_item_fit(itemw, x, totw, w-lastw, !item->next, litem->alignment, NULL); + else + neww= 0; /* no space left, all will need clamping to minimum size */ + + x += neww; + + if((neww < minw || itemw == minw) && w != 0) { + /* fixed size */ + item->flag= 1; + fixedw += minw; + flag= 1; + totw -= itemw; + } + else { + /* keep free size */ + item->flag= 0; + freew += itemw; + } + } + + lastw= fixedw; + } while(flag); + + freex= 0; + fixedx= 0; + x= litem->x; + + for(item=litem->items.first; item; item=item->next) { + ui_item_size(item, &itemw, &itemh); + minw= ui_litem_min_width(itemw); + + if(item->flag) { + /* fixed minimum size items */ + itemw= ui_item_fit(minw, fixedx, fixedw, MIN2(w, fixedw), !item->next, litem->alignment, NULL); + fixedx += itemw; + } + else { + /* free size item */ + itemw= ui_item_fit(itemw, freex, freew, w-fixedw, !item->next, litem->alignment, NULL); + freex += itemw; + } + + /* align right/center */ + offset= 0; + if(litem->alignment == UI_LAYOUT_ALIGN_RIGHT) { + if(fixedw == 0 && freew < w-fixedw) + offset= (w - fixedw) - freew; + } + else if(litem->alignment == UI_LAYOUT_ALIGN_CENTER) { + if(fixedw == 0 && freew < w-fixedw) + offset= ((w - fixedw) - freew)/2; + } + + /* position item */ + ui_item_position(item, x+offset, y-itemh, itemw, itemh); + + x += itemw; + if(item->next) + x += litem->space; + } + + litem->w= x - litem->x; + litem->h= litem->y - y; + litem->x= x; + litem->y= y; +} + +/* single-column layout */ +static void ui_litem_estimate_column(uiLayout *litem) +{ + uiItem *item; + int itemw, itemh; + + litem->w= 0; + litem->h= 0; + + for(item=litem->items.first; item; item=item->next) { + ui_item_size(item, &itemw, &itemh); + + litem->w= MAX2(litem->w, itemw); + litem->h += itemh; + + if(item->next) + litem->h += litem->space; + } +} + +static void ui_litem_layout_column(uiLayout *litem) +{ + uiItem *item; + int itemh, x, y; + + x= litem->x; + y= litem->y; + + for(item=litem->items.first; item; item=item->next) { + ui_item_size(item, NULL, &itemh); + + y -= itemh; + ui_item_position(item, x, y, litem->w, itemh); + + if(item->next) + y -= litem->space; + } + + litem->h= litem->y - y; + litem->x= x; + litem->y= y; +} + +/* root layout */ +static void ui_litem_estimate_root(uiLayout *litem) +{ + /* nothing to do */ +} + +static void ui_litem_layout_root(uiLayout *litem) +{ + if(litem->root->type == UI_LAYOUT_HEADER) + ui_litem_layout_row(litem); + else + ui_litem_layout_column(litem); +} + +/* box layout */ +static void ui_litem_estimate_box(uiLayout *litem) +{ + uiStyle *style= litem->root->style; + + ui_litem_estimate_column(litem); + litem->w += 2*style->boxspace; + litem->h += style->boxspace; +} + +static void ui_litem_layout_box(uiLayout *litem) +{ + uiLayoutItemBx *box= (uiLayoutItemBx*)litem; + uiStyle *style= litem->root->style; + uiBut *but; + int w, h; + + w= litem->w; + h= litem->h; + + litem->x += style->boxspace; + litem->y -= style->boxspace; + + if(w != 0) litem->w -= 2*style->boxspace; + if(h != 0) litem->h -= 2*style->boxspace; + + ui_litem_layout_column(litem); + + litem->x -= style->boxspace; + litem->y -= style->boxspace; + + if(w != 0) litem->w += 2*style->boxspace; + if(h != 0) litem->h += style->boxspace; + + /* roundbox around the sublayout */ + but= box->roundbox; + but->x1= litem->x; + but->y1= litem->y; + but->x2= litem->x+litem->w; + but->y2= litem->y+litem->h; +} + +/* multi-column layout, automatically flowing to the next */ +static void ui_litem_estimate_column_flow(uiLayout *litem) +{ + uiStyle *style= litem->root->style; + uiLayoutItemFlow *flow= (uiLayoutItemFlow*)litem; + uiItem *item; + int col, x, y, emh, emy, miny, itemw, itemh, maxw=0; + int toth, totitem; + + /* compute max needed width and total height */ + toth= 0; + totitem= 0; + for(item=litem->items.first; item; item=item->next) { + ui_item_size(item, &itemw, &itemh); + maxw= MAX2(maxw, itemw); + toth += itemh; + totitem++; + } + + if(flow->number <= 0) { + /* auto compute number of columns, not very good */ + if(maxw == 0) { + flow->totcol= 1; + return; + } + + flow->totcol= MAX2(litem->root->emw/maxw, 1); + flow->totcol= MIN2(flow->totcol, totitem); + } + else + flow->totcol= flow->number; + + /* compute sizes */ + x= 0; + y= 0; + emy= 0; + miny= 0; + + maxw= 0; + emh= toth/flow->totcol; + + /* create column per column */ + col= 0; + for(item=litem->items.first; item; item=item->next) { + ui_item_size(item, &itemw, &itemh); + + y -= itemh + style->buttonspacey; + miny= MIN2(miny, y); + emy -= itemh; + maxw= MAX2(itemw, maxw); + + /* decide to go to next one */ + if(col < flow->totcol-1 && emy <= -emh) { + x += maxw + litem->space; + maxw= 0; + y= 0; + col++; + } + } + + litem->h= litem->y - miny; +} + +static void ui_litem_layout_column_flow(uiLayout *litem) +{ + uiStyle *style= litem->root->style; + uiLayoutItemFlow *flow= (uiLayoutItemFlow*)litem; + uiItem *item; + int col, x, y, w, emh, emy, miny, itemw, itemh; + int toth, totitem, offset; + + /* compute max needed width and total height */ + toth= 0; + totitem= 0; + for(item=litem->items.first; item; item=item->next) { + ui_item_size(item, &itemw, &itemh); + toth += itemh; + totitem++; + } + + /* compute sizes */ + x= litem->x; + y= litem->y; + emy= 0; + miny= 0; + + w= litem->w - (flow->totcol-1)*style->columnspace; + emh= toth/flow->totcol; + + /* create column per column */ + col= 0; + for(item=litem->items.first; item; item=item->next) { + ui_item_size(item, NULL, &itemh); + itemw= ui_item_fit(1, x-litem->x, flow->totcol, w, col == flow->totcol-1, litem->alignment, &offset); + + y -= itemh; + emy -= itemh; + ui_item_position(item, x+offset, y, itemw, itemh); + y -= style->buttonspacey; + miny= MIN2(miny, y); + + /* decide to go to next one */ + if(col < flow->totcol-1 && emy <= -emh) { + x += itemw + style->columnspace; + y= litem->y; + col++; + } + } + + litem->h= litem->y - miny; + litem->x= x; + litem->y= miny; +} + +/* free layout */ +static void ui_litem_estimate_free(uiLayout *litem) +{ + uiItem *item; + int itemx, itemy, itemw, itemh, minx, miny; + + minx= 1e6; + miny= 1e6; + litem->w= 0; + litem->h= 0; + + for(item=litem->items.first; item; item=item->next) { + ui_item_offset(item, &itemx, &itemy); + ui_item_size(item, &itemw, &itemh); + + minx= MIN2(minx, itemx); + miny= MIN2(miny, itemy); + + litem->w= MAX2(litem->w, itemx+itemw); + litem->h= MAX2(litem->h, itemy+itemh); + } + + litem->w -= minx; + litem->h -= miny; +} + +static void ui_litem_layout_free(uiLayout *litem) +{ + uiItem *item; + float scalex=1.0f, scaley=1.0f; + int x, y, newx, newy, itemx, itemy, itemh, itemw, minx, miny, totw, toth; + + minx= 1e6; + miny= 1e6; + totw= 0; + toth= 0; + + for(item=litem->items.first; item; item=item->next) { + ui_item_offset(item, &itemx, &itemy); + ui_item_size(item, &itemw, &itemh); + + minx= MIN2(minx, itemx); + miny= MIN2(miny, itemy); + + totw= MAX2(totw, itemx+itemw); + toth= MAX2(toth, itemy+itemh); + } + + totw -= minx; + toth -= miny; + + if(litem->w && totw > litem->w) + scalex= (float)litem->w/(float)totw; + if(litem->h && toth > litem->h) + scaley= (float)litem->h/(float)toth; + + x= litem->x; + y= litem->y - scaley*toth; + + for(item=litem->items.first; item; item=item->next) { + ui_item_offset(item, &itemx, &itemy); + ui_item_size(item, &itemw, &itemh); + + if(scalex != 1.0f) { + newx= itemx*scalex; + itemw= (itemx + itemw)*scalex - newx; + itemx= newx; + } + + if(scaley != 1.0f) { + newy= itemy*scaley; + itemh= (itemy + itemh)*scaley - newy; + itemy= newy; + } + + ui_item_position(item, x+itemx-minx, y+itemy-miny, itemw, itemh); + } + + litem->w= scalex*totw; + litem->h= litem->y - y; + litem->x= x + litem->w; + litem->y= y; +} + +/* split layout */ +static void ui_litem_estimate_split(uiLayout *litem) +{ + ui_litem_estimate_row(litem); +} + +static void ui_litem_layout_split(uiLayout *litem) +{ + uiLayoutItemSplt *split= (uiLayoutItemSplt*)litem; + uiItem *item; + int itemh, x, y, w, tot=0, colw=0; + + x= litem->x; + y= litem->y; + + for(item=litem->items.first; item; item=item->next) + tot++; + + if(tot == 0) + return; + + w= (litem->w - (tot-1)*litem->space); + colw= w*split->percentage; + colw= MAX2(colw, 0); + + for(item=litem->items.first; item; item=item->next) { + ui_item_size(item, NULL, &itemh); + + ui_item_position(item, x, y-itemh, colw, itemh); + x += colw; + + if(item->next) { + colw= (w - (w*split->percentage))/(tot-1); + colw= MAX2(colw, 0); + + x += litem->space; + } + } + + litem->w= x - litem->x; + litem->h= litem->y - y; + litem->x= x; + litem->y= y; +} + +/* layout create functions */ +uiLayout *uiLayoutRow(uiLayout *layout, int align) +{ + uiLayout *litem; + + litem= MEM_callocN(sizeof(uiLayout), "uiLayoutRow"); + litem->item.type= ITEM_LAYOUT_ROW; + litem->root= layout->root; + litem->align= align; + litem->active= 1; + litem->enabled= 1; + litem->context= layout->context; + litem->space= (align)? 0: layout->root->style->buttonspacex; + BLI_addtail(&layout->items, litem); + + uiBlockSetCurLayout(layout->root->block, litem); + + return litem; +} + +uiLayout *uiLayoutColumn(uiLayout *layout, int align) +{ + uiLayout *litem; + + litem= MEM_callocN(sizeof(uiLayout), "uiLayoutColumn"); + litem->item.type= ITEM_LAYOUT_COLUMN; + litem->root= layout->root; + litem->align= align; + litem->active= 1; + litem->enabled= 1; + litem->context= layout->context; + litem->space= (litem->align)? 0: layout->root->style->buttonspacey; + BLI_addtail(&layout->items, litem); + + uiBlockSetCurLayout(layout->root->block, litem); + + return litem; +} + +uiLayout *uiLayoutColumnFlow(uiLayout *layout, int number, int align) +{ + uiLayoutItemFlow *flow; + + flow= MEM_callocN(sizeof(uiLayoutItemFlow), "uiLayoutItemFlow"); + flow->litem.item.type= ITEM_LAYOUT_COLUMN_FLOW; + flow->litem.root= layout->root; + flow->litem.align= align; + flow->litem.active= 1; + flow->litem.enabled= 1; + flow->litem.context= layout->context; + flow->litem.space= (flow->litem.align)? 0: layout->root->style->columnspace; + flow->number= number; + BLI_addtail(&layout->items, flow); + + uiBlockSetCurLayout(layout->root->block, &flow->litem); + + return &flow->litem; +} + +uiLayout *uiLayoutBox(uiLayout *layout) +{ + uiLayoutItemBx *box; + + box= MEM_callocN(sizeof(uiLayoutItemBx), "uiLayoutItemBx"); + box->litem.item.type= ITEM_LAYOUT_BOX; + box->litem.root= layout->root; + box->litem.active= 1; + box->litem.enabled= 1; + box->litem.context= layout->context; + box->litem.space= layout->root->style->columnspace; + BLI_addtail(&layout->items, box); + + uiBlockSetCurLayout(layout->root->block, &box->litem); + + box->roundbox= uiDefBut(layout->root->block, ROUNDBOX, 0, "", 0, 0, 0, 0, NULL, 0.0, 0.0, 0, 0, ""); + + return &box->litem; +} + +uiLayout *uiLayoutFree(uiLayout *layout, int align) +{ + uiLayout *litem; + + litem= MEM_callocN(sizeof(uiLayout), "uiLayoutFree"); + litem->item.type= ITEM_LAYOUT_FREE; + litem->root= layout->root; + litem->align= align; + litem->active= 1; + litem->enabled= 1; + litem->context= layout->context; + BLI_addtail(&layout->items, litem); + + uiBlockSetCurLayout(layout->root->block, litem); + + return litem; +} + +uiBlock *uiLayoutFreeBlock(uiLayout *layout) +{ + uiBlock *block; + + block= uiLayoutGetBlock(layout); + uiLayoutFree(layout, 0); + + return block; +} + +uiLayout *uiLayoutSplit(uiLayout *layout, float percentage) +{ + uiLayoutItemSplt *split; + + split= MEM_callocN(sizeof(uiLayoutItemSplt), "uiLayoutItemSplt"); + split->litem.item.type= ITEM_LAYOUT_SPLIT; + split->litem.root= layout->root; + split->litem.active= 1; + split->litem.enabled= 1; + split->litem.context= layout->context; + split->litem.space= layout->root->style->columnspace; + split->percentage= (percentage == 0.0f)? 0.5f: percentage; + BLI_addtail(&layout->items, split); + + uiBlockSetCurLayout(layout->root->block, &split->litem); + + return &split->litem; +} + +void uiLayoutSetActive(uiLayout *layout, int active) +{ + layout->active= active; +} + +void uiLayoutSetEnabled(uiLayout *layout, int enabled) +{ + layout->enabled= enabled; +} + +void uiLayoutSetRedAlert(uiLayout *layout, int redalert) +{ + layout->redalert= redalert; +} + +void uiLayoutSetKeepAspect(uiLayout *layout, int keepaspect) +{ + layout->keepaspect= keepaspect; +} + +void uiLayoutSetAlignment(uiLayout *layout, int alignment) +{ + layout->alignment= alignment; +} + +void uiLayoutSetScaleX(uiLayout *layout, float scale) +{ + layout->scale[0]= scale; +} + +void uiLayoutSetScaleY(uiLayout *layout, float scale) +{ + layout->scale[1]= scale; +} + +int uiLayoutGetActive(uiLayout *layout) +{ + return layout->active; +} + +int uiLayoutGetEnabled(uiLayout *layout) +{ + return layout->enabled; +} + +int uiLayoutGetRedAlert(uiLayout *layout) +{ + return layout->redalert; +} + +int uiLayoutGetKeepAspect(uiLayout *layout) +{ + return layout->keepaspect; +} + +int uiLayoutGetAlignment(uiLayout *layout) +{ + return layout->alignment; +} + +float uiLayoutGetScaleX(uiLayout *layout) +{ + return layout->scale[0]; +} + +float uiLayoutGetScaleY(uiLayout *layout) +{ + return layout->scale[0]; +} + +/********************** Layout *******************/ + +static void ui_item_scale(uiLayout *litem, float scale[2]) +{ + uiItem *item; + int x, y, w, h; + + for(item=litem->items.last; item; item=item->prev) { + ui_item_size(item, &w, &h); + ui_item_offset(item, &x, &y); + + if(scale[0] != 0.0f) { + x *= scale[0]; + w *= scale[0]; + } + + if(scale[1] != 0.0f) { + y *= scale[1]; + h *= scale[1]; + } + + ui_item_position(item, x, y, w, h); + } +} + +static void ui_item_estimate(uiItem *item) +{ + uiItem *subitem; + + if(item->type != ITEM_BUTTON) { + uiLayout *litem= (uiLayout*)item; + + for(subitem=litem->items.first; subitem; subitem=subitem->next) + ui_item_estimate(subitem); + + if(litem->items.first == NULL) + return; + + if(litem->scale[0] != 0.0f || litem->scale[1] != 0.0f) + ui_item_scale(litem, litem->scale); + + switch(litem->item.type) { + case ITEM_LAYOUT_COLUMN: + ui_litem_estimate_column(litem); + break; + case ITEM_LAYOUT_COLUMN_FLOW: + ui_litem_estimate_column_flow(litem); + break; + case ITEM_LAYOUT_ROW: + ui_litem_estimate_row(litem); + break; + case ITEM_LAYOUT_BOX: + ui_litem_estimate_box(litem); + break; + case ITEM_LAYOUT_ROOT: + ui_litem_estimate_root(litem); + break; + case ITEM_LAYOUT_FREE: + ui_litem_estimate_free(litem); + break; + case ITEM_LAYOUT_SPLIT: + ui_litem_estimate_split(litem); + break; + default: + break; + } + } +} + +static void ui_item_align(uiLayout *litem, int nr) +{ + uiItem *item; + uiButtonItem *bitem; + uiLayoutItemBx *box; + + for(item=litem->items.last; item; item=item->prev) { + if(item->type == ITEM_BUTTON) { + bitem= (uiButtonItem*)item; + if(ui_but_can_align(bitem->but)) + if(!bitem->but->alignnr) + bitem->but->alignnr= nr; + } + else if(item->type == ITEM_LAYOUT_FREE); + else if(item->type == ITEM_LAYOUT_BOX) { + box= (uiLayoutItemBx*)item; + box->roundbox->alignnr= nr; + BLI_remlink(&litem->root->block->buttons, box->roundbox); + BLI_addhead(&litem->root->block->buttons, box->roundbox); + } + else + ui_item_align((uiLayout*)item, nr); + } +} + +static void ui_item_flag(uiLayout *litem, int flag) +{ + uiItem *item; + uiButtonItem *bitem; + + for(item=litem->items.last; item; item=item->prev) { + if(item->type == ITEM_BUTTON) { + bitem= (uiButtonItem*)item; + bitem->but->flag |= flag; + } + else + ui_item_flag((uiLayout*)item, flag); + } +} + +static void ui_item_layout(uiItem *item) +{ + uiItem *subitem; + + if(item->type != ITEM_BUTTON) { + uiLayout *litem= (uiLayout*)item; + + if(litem->items.first == NULL) + return; + + if(litem->align) + ui_item_align(litem, ++litem->root->block->alignnr); + if(!litem->active) + ui_item_flag(litem, UI_BUT_INACTIVE); + if(!litem->enabled) + ui_item_flag(litem, UI_BUT_DISABLED); + + switch(litem->item.type) { + case ITEM_LAYOUT_COLUMN: + ui_litem_layout_column(litem); + break; + case ITEM_LAYOUT_COLUMN_FLOW: + ui_litem_layout_column_flow(litem); + break; + case ITEM_LAYOUT_ROW: + ui_litem_layout_row(litem); + break; + case ITEM_LAYOUT_BOX: + ui_litem_layout_box(litem); + break; + case ITEM_LAYOUT_ROOT: + ui_litem_layout_root(litem); + break; + case ITEM_LAYOUT_FREE: + ui_litem_layout_free(litem); + break; + case ITEM_LAYOUT_SPLIT: + ui_litem_layout_split(litem); + break; + default: + break; + } + + for(subitem=litem->items.first; subitem; subitem=subitem->next) + ui_item_layout(subitem); + } +} + +static void ui_layout_items(const bContext *C, uiBlock *block, uiLayout *layout) +{ + ui_item_estimate(&layout->item); + ui_item_layout(&layout->item); +} + +static void ui_layout_end(const bContext *C, uiBlock *block, uiLayout *layout, int *x, int *y) +{ + if(layout->root->handlefunc) + uiBlockSetButmFunc(block, layout->root->handlefunc, layout->root->argv); + + ui_layout_items(C, block, layout); + + if(x) *x= layout->x; + if(y) *y= layout->y; +} + +static void ui_layout_free(uiLayout *layout) +{ + uiItem *item, *next; + + for(item=layout->items.first; item; item=next) { + next= item->next; + + if(item->type == ITEM_BUTTON) + MEM_freeN(item); + else + ui_layout_free((uiLayout*)item); + } + + MEM_freeN(layout); +} + +uiLayout *uiBlockLayout(uiBlock *block, int dir, int type, int x, int y, int size, int em, uiStyle *style) +{ + uiLayout *layout; + uiLayoutRoot *root; + + root= MEM_callocN(sizeof(uiLayoutRoot), "uiLayoutRoot"); + root->type= type; + root->style= style; + root->block= block; + root->opcontext= WM_OP_INVOKE_REGION_WIN; + + layout= MEM_callocN(sizeof(uiLayout), "uiLayout"); + layout->item.type= ITEM_LAYOUT_ROOT; + + layout->x= x; + layout->y= y; + layout->root= root; + layout->space= style->templatespace; + layout->active= 1; + layout->enabled= 1; + layout->context= NULL; + + if(type == UI_LAYOUT_MENU) + layout->space= 0; + + if(dir == UI_LAYOUT_HORIZONTAL) { + layout->h= size; + layout->root->emh= em*UI_UNIT_Y; + } + else { + layout->w= size; + layout->root->emw= em*UI_UNIT_X; + } + + block->curlayout= layout; + root->layout= layout; + BLI_addtail(&block->layouts, root); + + return layout; +} + +uiBlock *uiLayoutGetBlock(uiLayout *layout) +{ + return layout->root->block; +} + +void uiBlockSetCurLayout(uiBlock *block, uiLayout *layout) +{ + block->curlayout= layout; +} + +void ui_layout_add_but(uiLayout *layout, uiBut *but) +{ + uiButtonItem *bitem; + + bitem= MEM_callocN(sizeof(uiButtonItem), "uiButtonItem"); + bitem->item.type= ITEM_BUTTON; + bitem->but= but; + BLI_addtail(&layout->items, bitem); + + if(layout->context) { + but->context= layout->context; + but->context->used= 1; + } +} + +void uiLayoutSetOperatorContext(uiLayout *layout, int opcontext) +{ + layout->root->opcontext= opcontext; +} + +void uiLayoutSetFunc(uiLayout *layout, uiMenuHandleFunc handlefunc, void *argv) +{ + layout->root->handlefunc= handlefunc; + layout->root->argv= argv; +} + +void uiBlockLayoutResolve(const bContext *C, uiBlock *block, int *x, int *y) +{ + uiLayoutRoot *root; + + if(x) *x= 0; + if(y) *y= 0; + + block->curlayout= NULL; + + for(root=block->layouts.first; root; root=root->next) { + /* NULL in advance so we don't interfere when adding button */ + ui_layout_end(C, block, root->layout, x, y); + ui_layout_free(root->layout); + } + + BLI_freelistN(&block->layouts); + + /* XXX silly trick, interface_templates.c doesn't get linked + * because it's not used by other files in this module? */ + { + void ui_template_fix_linking(); + ui_template_fix_linking(); + } +} + +void uiLayoutSetContextPointer(uiLayout *layout, char *name, PointerRNA *ptr) +{ + uiBlock *block= layout->root->block; + layout->context= CTX_store_add(&block->contexts, name, ptr); +} + diff --git a/source/blender/editors/interface/interface_panel.c b/source/blender/editors/interface/interface_panel.c new file mode 100644 index 00000000000..278f7c026b1 --- /dev/null +++ b/source/blender/editors/interface/interface_panel.c @@ -0,0 +1,1378 @@ +/** + * $Id$ + * + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + * + * Contributor(s): Blender Foundation, 2003-2009 full recode. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/* a full doc with API notes can be found in bf-blender/blender/doc/interface_API.txt */ + +#include <math.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + +#include "MEM_guardedalloc.h" + +#include "PIL_time.h" + +#include "BLI_blenlib.h" +#include "BLI_arithb.h" + +#include "DNA_screen_types.h" +#include "DNA_space_types.h" +#include "DNA_userdef_types.h" + +#include "BKE_context.h" +#include "BKE_screen.h" +#include "BKE_utildefines.h" + +#include "BIF_gl.h" +#include "BIF_glutil.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_screen.h" + +#include "UI_interface.h" +#include "UI_view2d.h" + +#include "interface_intern.h" + +/*********************** defines and structs ************************/ + +#define ANIMATION_TIME 0.30 +#define ANIMATION_INTERVAL 0.02 + +#define PNL_LAST_ADDED 1 +#define PNL_ACTIVE 2 +#define PNL_WAS_ACTIVE 4 +#define PNL_ANIM_ALIGN 8 + +typedef enum uiHandlePanelState { + PANEL_STATE_DRAG, + PANEL_STATE_DRAG_SCALE, + PANEL_STATE_WAIT_UNTAB, + PANEL_STATE_ANIMATION, + PANEL_STATE_EXIT +} uiHandlePanelState; + +typedef struct uiHandlePanelData { + uiHandlePanelState state; + + /* animation */ + wmTimer *animtimer; + double starttime; + + /* dragging */ + int startx, starty; + int startofsx, startofsy; + int startsizex, startsizey; +} uiHandlePanelData; + +static void panel_activate_state(const bContext *C, Panel *pa, uiHandlePanelState state); + +/*********************** space specific code ************************/ +/* temporary code to remove all sbuts stuff from panel code */ + +static int panel_aligned(ScrArea *sa, ARegion *ar) +{ + if(sa->spacetype==SPACE_BUTS && ar->regiontype == RGN_TYPE_WINDOW) { + SpaceButs *sbuts= sa->spacedata.first; + return sbuts->align; + } + else if(ar->regiontype==RGN_TYPE_UI) + return BUT_VERTICAL; + + return 0; +} + +static int panels_re_align(ScrArea *sa, ARegion *ar, Panel **r_pa) +{ + Panel *pa; + int active= 0; + + *r_pa= NULL; + + if(sa->spacetype==SPACE_BUTS && ar->regiontype == RGN_TYPE_WINDOW) { + SpaceButs *sbuts= sa->spacedata.first; + + if(sbuts->align) + if(sbuts->re_align || sbuts->mainbo!=sbuts->mainb) + return 1; + } + else if(ar->regiontype==RGN_TYPE_UI) + return 1; + + /* in case panel is added or disappears */ + for(pa=ar->panels.first; pa; pa=pa->next) { + if((pa->runtime_flag & PNL_WAS_ACTIVE) && !(pa->runtime_flag & PNL_ACTIVE)) + return 1; + if(!(pa->runtime_flag & PNL_WAS_ACTIVE) && (pa->runtime_flag & PNL_ACTIVE)) + return 1; + if(pa->activedata) + active= 1; + } + + /* in case we need to do an animation (size changes) */ + for(pa=ar->panels.first; pa; pa=pa->next) { + if(pa->runtime_flag & PNL_ANIM_ALIGN) { + if(!active) + *r_pa= pa; + return 1; + } + } + + return 0; +} + +/****************************** panels ******************************/ + +static void ui_panel_copy_offset(Panel *pa, Panel *papar) +{ + /* with respect to sizes... papar is parent */ + + pa->ofsx= papar->ofsx; + pa->ofsy= papar->ofsy + papar->sizey-pa->sizey; +} + +Panel *uiBeginPanel(ARegion *ar, uiBlock *block, PanelType *pt, int *open) +{ + uiStyle *style= U.uistyles.first; + Panel *pa, *patab, *palast, *panext; + char *panelname= pt->label; + char *tabname= pt->label; + char *hookname= NULL; + int newpanel; + + /* check if Panel exists, then use that one */ + for(pa=ar->panels.first; pa; pa=pa->next) + if(strncmp(pa->panelname, panelname, UI_MAX_NAME_STR)==0) + if(strncmp(pa->tabname, tabname, UI_MAX_NAME_STR)==0) + break; + + newpanel= (pa == NULL); + + if(!newpanel) { + pa->type= pt; + } + else { + /* new panel */ + pa= MEM_callocN(sizeof(Panel), "new panel"); + pa->type= pt; + BLI_strncpy(pa->panelname, panelname, UI_MAX_NAME_STR); + BLI_strncpy(pa->tabname, tabname, UI_MAX_NAME_STR); + + pa->ofsx= 0; + pa->ofsy= style->panelouter; + pa->sizex= 0; + pa->sizey= 0; + + BLI_addtail(&ar->panels, pa); + + /* make new Panel tabbed? */ + if(hookname) { + for(patab= ar->panels.first; patab; patab= patab->next) { + if((patab->runtime_flag & PNL_ACTIVE) && patab->paneltab==NULL) { + if(strncmp(hookname, patab->panelname, UI_MAX_NAME_STR)==0) { + if(strncmp(tabname, patab->tabname, UI_MAX_NAME_STR)==0) { + pa->paneltab= patab; + ui_panel_copy_offset(pa, patab); + break; + } + } + } + } + } + } + + /* if a new panel is added, we insert it right after the panel + * that was last added. this way new panels are inserted in the + * right place between versions */ + for(palast=ar->panels.first; palast; palast=palast->next) + if(palast->runtime_flag & PNL_LAST_ADDED) + break; + + if(newpanel) { + pa->sortorder= (palast)? palast->sortorder+1: 0; + + for(panext=ar->panels.first; panext; panext=panext->next) + if(panext != pa && panext->sortorder >= pa->sortorder) + panext->sortorder++; + } + + if(palast) + palast->runtime_flag &= ~PNL_LAST_ADDED; + + /* assign to block */ + block->panel= pa; + pa->runtime_flag |= PNL_ACTIVE|PNL_LAST_ADDED; + + *open= 0; + + if(pa->paneltab) return pa; + if(pa->flag & PNL_CLOSED) return pa; + + *open= 1; + pa->drawname[0]= 0; /* otherwise closes panels show wrong title */ + + return pa; +} + +void uiEndPanel(uiBlock *block, int width, int height) +{ + Panel *pa= block->panel; + + if(pa->sizex != width || pa->sizey != height) { + pa->runtime_flag |= PNL_ANIM_ALIGN; + pa->ofsy += pa->sizey-height; + } + + pa->sizex= width; + pa->sizey= height; +} + +#if 0 +void uiPanelToMouse(const bContext *C, Panel *pa) +{ + /* global control over this feature; UI_PNL_TO_MOUSE only called for hotkey panels */ + if(U.uiflag & USER_PANELPINNED); + else if(pa->control & UI_PNL_TO_MOUSE) { + int mx, my; + + mx= CTX_wm_window(C)->eventstate->x; + my= CTX_wm_window(C)->eventstate->y; + + pa->ofsx= mx-pa->sizex/2; + pa->ofsy= my-pa->sizey/2; + + if(pa->flag & PNL_CLOSED) pa->flag &= ~PNL_CLOSED; + } + + if(pa->control & UI_PNL_UNSTOW) { + if(pa->flag & PNL_CLOSEDY) { + pa->flag &= ~PNL_CLOSED; + } + } +} +#endif + +static int panel_has_tabs(ARegion *ar, Panel *panel) +{ + Panel *pa= ar->panels.first; + + if(panel==NULL) return 0; + + while(pa) { + if((pa->runtime_flag & PNL_ACTIVE) && pa->paneltab==panel) { + return 1; + } + pa= pa->next; + } + return 0; +} + +static void ui_offset_panel_block(uiBlock *block) +{ + uiStyle *style= U.uistyles.first; + uiBut *but; + int ofsy; + + /* compute bounds and offset */ + ui_bounds_block(block); + + ofsy= block->panel->sizey - style->panelspace; + + for(but= block->buttons.first; but; but=but->next) { + but->y1 += ofsy; + but->y2 += ofsy; + } + + block->maxx= block->panel->sizex; + block->maxy= block->panel->sizey; + block->minx= block->miny= 0.0; +} + +/**************************** drawing *******************************/ + +/* extern used by previewrender */ +void uiPanelPush(uiBlock *block) +{ + glPushMatrix(); + + if(block->panel) + glTranslatef((float)block->panel->ofsx, (float)block->panel->ofsy, 0.0); +} + +void uiPanelPop(uiBlock *block) +{ + glPopMatrix(); +} + +/* triangle 'icon' for panel header */ +void ui_draw_tria_icon(float x, float y, char dir) +{ + if(dir=='h') { + ui_draw_anti_tria(x-1, y, x-1, y+11.0, x+9, y+6.25); + } + else { + ui_draw_anti_tria(x-3, y+10, x+8-1, y+10, x+4.25-2, y); + } +} + +/* triangle 'icon' inside rect */ +void ui_draw_tria_rect(rctf *rect, char dir) +{ + if(dir=='h') { + float half= 0.5f*(rect->ymax - rect->ymin); + ui_draw_anti_tria(rect->xmin, rect->ymin, rect->xmin, rect->ymax, rect->xmax, rect->ymin+half); + } + else { + float half= 0.5f*(rect->xmax - rect->xmin); + ui_draw_anti_tria(rect->xmin, rect->ymax, rect->xmax, rect->ymax, rect->xmin+half, rect->ymin); + } +} + +void ui_draw_anti_x(float x1, float y1, float x2, float y2) +{ + + /* set antialias line */ + glEnable(GL_LINE_SMOOTH); + glEnable(GL_BLEND); + + glLineWidth(2.0); + + fdrawline(x1, y1, x2, y2); + fdrawline(x1, y2, x2, y1); + + glLineWidth(1.0); + + glDisable(GL_LINE_SMOOTH); + glDisable(GL_BLEND); + +} + +/* x 'icon' for panel header */ +static void ui_draw_x_icon(float x, float y) +{ + + ui_draw_anti_x(x, y, x+9.375, y+9.375); + +} + +#define PNL_ICON 20 + +static void ui_draw_panel_scalewidget(rcti *rect) +{ + float xmin, xmax, dx; + float ymin, ymax, dy; + + xmin= rect->xmax-PNL_HEADER+2; + xmax= rect->xmax-3; + ymin= rect->ymin+3; + ymax= rect->ymin+PNL_HEADER-2; + + dx= 0.5f*(xmax-xmin); + dy= 0.5f*(ymax-ymin); + + glEnable(GL_BLEND); + glColor4ub(255, 255, 255, 50); + fdrawline(xmin, ymin, xmax, ymax); + fdrawline(xmin+dx, ymin, xmax, ymax-dy); + + glColor4ub(0, 0, 0, 50); + fdrawline(xmin, ymin+1, xmax, ymax+1); + fdrawline(xmin+dx, ymin+1, xmax, ymax-dy+1); + glDisable(GL_BLEND); +} + +static void ui_draw_panel_dragwidget(rctf *rect) +{ + float xmin, xmax, dx; + float ymin, ymax, dy; + + xmin= rect->xmin; + xmax= rect->xmax; + ymin= rect->ymin; + ymax= rect->ymax; + + dx= 0.333f*(xmax-xmin); + dy= 0.333f*(ymax-ymin); + + glEnable(GL_BLEND); + glColor4ub(255, 255, 255, 50); + fdrawline(xmin, ymax, xmax, ymin); + fdrawline(xmin+dx, ymax, xmax, ymin+dy); + fdrawline(xmin+2*dx, ymax, xmax, ymin+2*dy); + + glColor4ub(0, 0, 0, 50); + fdrawline(xmin, ymax+1, xmax, ymin+1); + fdrawline(xmin+dx, ymax+1, xmax, ymin+dy+1); + fdrawline(xmin+2*dx, ymax+1, xmax, ymin+2*dy+1); + glDisable(GL_BLEND); +} + + +static void ui_draw_aligned_panel_header(ARegion *ar, uiStyle *style, uiBlock *block, rcti *rect) +{ + Panel *panel= block->panel; + Panel *pa; + rcti hrect; + float width; + int a, nr= 1, pnl_icons; + char *activename= panel->drawname[0]?panel->drawname:panel->panelname; + char *panelname; + + /* count */ + for(pa= ar->panels.first; pa; pa=pa->next) + if(pa->runtime_flag & PNL_ACTIVE) + if(pa->paneltab==panel) + nr++; + + /* + 0.001f to avoid flirting with float inaccuracy */ + if(panel->control & UI_PNL_CLOSE) pnl_icons=(panel->labelofs+2*PNL_ICON+5)/block->aspect + 0.001f; + else pnl_icons= (panel->labelofs+PNL_ICON+5)/block->aspect + 0.001f; + + if(nr==1) { + + /* active tab */ + /* draw text label */ + UI_ThemeColor(TH_TITLE); + + hrect= *rect; + hrect.xmin= rect->xmin+pnl_icons; + uiStyleFontDraw(&style->paneltitle, &hrect, activename); + + return; + } + + a= 0; + width= (rect->xmax-rect->xmin - 3 - pnl_icons - PNL_ICON)/nr; + for(pa= ar->panels.first; pa; pa=pa->next) { + panelname= pa->drawname[0]?pa->drawname:pa->panelname; + + if((pa->runtime_flag & PNL_ACTIVE) && (pa==panel || pa->paneltab==panel)) { + float col[3]; + + UI_GetThemeColor3fv(TH_TITLE, col); + + /* active tab */ + if(pa==panel) + glColor4f(col[0], col[1], col[2], 1.0f); + else + glColor4f(col[0], col[1], col[2], 0.5f); + + hrect= *rect; + hrect.xmin= rect->xmin+pnl_icons + a*width; + hrect.xmax= hrect.xmin + width; + uiStyleFontDraw(&style->paneltitle, &hrect, panelname); + + a++; + } + } +} + +static void rectf_scale(rctf *rect, float scale) +{ + float centx= 0.5f*(rect->xmin+rect->xmax); + float centy= 0.5f*(rect->ymin+rect->ymax); + float sizex= 0.5f*scale*(rect->xmax - rect->xmin); + float sizey= 0.5f*scale*(rect->ymax - rect->ymin); + + rect->xmin= centx - sizex; + rect->xmax= centx + sizex; + rect->ymin= centy - sizey; + rect->ymax= centy + sizey; +} + +/* panel integrated in buttonswindow, tool/property lists etc */ +void ui_draw_aligned_panel(ARegion *ar, uiStyle *style, uiBlock *block, rcti *rect) +{ + Panel *panel= block->panel, *prev; + rcti headrect; + rctf itemrect; + int ofsx; + + if(panel->paneltab) return; + + /* calculate header rect */ + /* + 0.001f to prevent flicker due to float inaccuracy */ + headrect= *rect; + headrect.ymin= headrect.ymax; + headrect.ymax= headrect.ymin + floor(PNL_HEADER/block->aspect + 0.001f); + + /* divider only when there's a previous panel */ + prev= panel->prev; + while(prev) { + if(prev->runtime_flag & PNL_ACTIVE) break; + prev= prev->prev; + } + + if(panel->sortorder != 0) { + float minx= rect->xmin+5.0f/block->aspect; + float maxx= rect->xmax-5.0f/block->aspect; + float y= headrect.ymax; + + glEnable(GL_BLEND); + glColor4f(0.0f, 0.0f, 0.0f, 0.5f); + fdrawline(minx, y+1, maxx, y+1); + glColor4f(1.0f, 1.0f, 1.0f, 0.25f); + fdrawline(minx, y, maxx, y); + glDisable(GL_BLEND); + } + + /* title */ + if(!(panel->flag & PNL_CLOSEDX)) { + ui_draw_aligned_panel_header(ar, style, block, &headrect); + + /* itemrect smaller */ + itemrect.xmax= headrect.xmax - 5.0f/block->aspect; + itemrect.xmin= itemrect.xmax - (headrect.ymax-headrect.ymin); + itemrect.ymin= headrect.ymin; + itemrect.ymax= headrect.ymax; + rectf_scale(&itemrect, 0.8f); + ui_draw_panel_dragwidget(&itemrect); + } + + /* if the panel is minimized vertically: + * (------) + */ + if(panel->flag & PNL_CLOSEDY) { + + + /* if it's being overlapped by a panel being dragged */ + if(panel->flag & PNL_OVERLAP) { + UI_ThemeColor(TH_TEXT_HI); + uiRoundRect(rect->xmin, rect->ymax, rect->xmax, rect->ymax+PNL_HEADER, 8); + } + + } + else if(panel->flag & PNL_CLOSEDX) { + + } + /* an open panel */ + else { + + /* in some occasions, draw a border */ + if(panel->flag & PNL_SELECT) { + if(panel->control & UI_PNL_SOLID) uiSetRoundBox(15); + else uiSetRoundBox(3); + + UI_ThemeColorShade(TH_BACK, -120); + uiRoundRect(0.5f + rect->xmin, 0.5f + rect->ymin, 0.5f + rect->xmax, 0.5f + headrect.ymax+1, 8); + } + if(panel->flag & PNL_OVERLAP) { + if(panel->control & UI_PNL_SOLID) uiSetRoundBox(15); + else uiSetRoundBox(3); + + UI_ThemeColor(TH_TEXT_HI); + uiRoundRect(rect->xmin, rect->ymin, rect->xmax, headrect.ymax+1, 8); + } + + if(panel->control & UI_PNL_SCALE) + ui_draw_panel_scalewidget(rect); + } + + /* draw optional close icon */ + + ofsx= 6; + if(panel->control & UI_PNL_CLOSE) { + + UI_ThemeColor(TH_TEXT); + ui_draw_x_icon(rect->xmin+2+ofsx, rect->ymax+2); + ofsx= 22; + } + + /* draw collapse icon */ + UI_ThemeColor(TH_TEXT); + + /* itemrect smaller */ + itemrect.xmin= headrect.xmin + 5.0f/block->aspect; + itemrect.xmax= itemrect.xmin + (headrect.ymax-headrect.ymin); + itemrect.ymin= headrect.ymin; + itemrect.ymax= headrect.ymax; + + rectf_scale(&itemrect, 0.5f); + + if(panel->flag & PNL_CLOSEDY) + ui_draw_tria_rect(&itemrect, 'h'); + else if(panel->flag & PNL_CLOSEDX) + ui_draw_tria_rect(&itemrect, 'h'); + else + ui_draw_tria_rect(&itemrect, 'v'); + + +} + +/************************** panel alignment *************************/ + +/* this function is needed because uiBlock and Panel itself dont +change sizey or location when closed */ +static int get_panel_real_ofsy(Panel *pa) +{ + if(pa->flag & PNL_CLOSEDY) return pa->ofsy+pa->sizey; + else if(pa->paneltab && (pa->paneltab->flag & PNL_CLOSEDY)) return pa->ofsy+pa->sizey; + else if(pa->paneltab) return pa->paneltab->ofsy; + else return pa->ofsy; +} + +static int get_panel_real_ofsx(Panel *pa) +{ + if(pa->flag & PNL_CLOSEDX) return pa->ofsx+PNL_HEADER; + else if(pa->paneltab && (pa->paneltab->flag & PNL_CLOSEDX)) return pa->ofsx+PNL_HEADER; + else return pa->ofsx+pa->sizex; +} + +typedef struct PanelSort { + Panel *pa, *orig; +} PanelSort; + +/* note about sorting; + the sortorder has a lower value for new panels being added. + however, that only works to insert a single panel, when more new panels get + added the coordinates of existing panels and the previously stored to-be-insterted + panels do not match for sorting */ + +static int find_leftmost_panel(const void *a1, const void *a2) +{ + const PanelSort *ps1=a1, *ps2=a2; + + if(ps1->pa->ofsx > ps2->pa->ofsx) return 1; + else if(ps1->pa->ofsx < ps2->pa->ofsx) return -1; + else if(ps1->pa->sortorder > ps2->pa->sortorder) return 1; + else if(ps1->pa->sortorder < ps2->pa->sortorder) return -1; + + return 0; +} + + +static int find_highest_panel(const void *a1, const void *a2) +{ + const PanelSort *ps1=a1, *ps2=a2; + + if(ps1->pa->ofsy+ps1->pa->sizey < ps2->pa->ofsy+ps2->pa->sizey) return 1; + else if(ps1->pa->ofsy+ps1->pa->sizey > ps2->pa->ofsy+ps2->pa->sizey) return -1; + else if(ps1->pa->sortorder > ps2->pa->sortorder) return 1; + else if(ps1->pa->sortorder < ps2->pa->sortorder) return -1; + + return 0; +} + +static int compare_panel(const void *a1, const void *a2) +{ + const PanelSort *ps1=a1, *ps2=a2; + + if(ps1->pa->sortorder > ps2->pa->sortorder) return 1; + else if(ps1->pa->sortorder < ps2->pa->sortorder) return -1; + + return 0; +} + +/* this doesnt draw */ +/* returns 1 when it did something */ +int uiAlignPanelStep(ScrArea *sa, ARegion *ar, float fac, int drag) +{ + uiStyle *style= U.uistyles.first; + Panel *pa; + PanelSort *ps, *panelsort, *psnext; + int a, tot=0, done; + int align= panel_aligned(sa, ar); + + /* count active, not tabbed panels */ + for(pa= ar->panels.first; pa; pa= pa->next) + if((pa->runtime_flag & PNL_ACTIVE) && pa->paneltab==NULL) + tot++; + + if(tot==0) return 0; + + /* extra; change close direction? */ + for(pa= ar->panels.first; pa; pa= pa->next) { + if((pa->runtime_flag & PNL_ACTIVE) && pa->paneltab==NULL) { + if((pa->flag & PNL_CLOSEDX) && (align==BUT_VERTICAL)) + pa->flag ^= PNL_CLOSED; + else if((pa->flag & PNL_CLOSEDY) && (align==BUT_HORIZONTAL)) + pa->flag ^= PNL_CLOSED; + } + } + + /* sort panels */ + panelsort= MEM_callocN(tot*sizeof(PanelSort), "panelsort"); + + ps= panelsort; + for(pa= ar->panels.first; pa; pa= pa->next) { + if((pa->runtime_flag & PNL_ACTIVE) && pa->paneltab==NULL) { + ps->pa= MEM_dupallocN(pa); + ps->orig= pa; + ps++; + } + } + + if(drag) { + /* while we are dragging, we sort on location and update sortorder */ + if(align==BUT_VERTICAL) + qsort(panelsort, tot, sizeof(PanelSort), find_highest_panel); + else + qsort(panelsort, tot, sizeof(PanelSort), find_leftmost_panel); + + for(ps=panelsort, a=0; a<tot; a++, ps++) + ps->orig->sortorder= a; + } + else + /* otherwise use sortorder */ + qsort(panelsort, tot, sizeof(PanelSort), compare_panel); + + /* no smart other default start loc! this keeps switching f5/f6/etc compatible */ + ps= panelsort; + ps->pa->ofsx= 0; + ps->pa->ofsy= -ps->pa->sizey-PNL_HEADER-style->panelouter; + + for(a=0; a<tot-1; a++, ps++) { + psnext= ps+1; + + if(align==BUT_VERTICAL) { + psnext->pa->ofsx= ps->pa->ofsx; + psnext->pa->ofsy= get_panel_real_ofsy(ps->pa) - psnext->pa->sizey-PNL_HEADER-style->panelouter; + } + else { + psnext->pa->ofsx= get_panel_real_ofsx(ps->pa); + psnext->pa->ofsy= ps->pa->ofsy + ps->pa->sizey - psnext->pa->sizey; + } + } + + /* we interpolate */ + done= 0; + ps= panelsort; + for(a=0; a<tot; a++, ps++) { + if((ps->pa->flag & PNL_SELECT)==0) { + if((ps->orig->ofsx != ps->pa->ofsx) || (ps->orig->ofsy != ps->pa->ofsy)) { + ps->orig->ofsx= floor(0.5 + fac*ps->pa->ofsx + (1.0-fac)*ps->orig->ofsx); + ps->orig->ofsy= floor(0.5 + fac*ps->pa->ofsy + (1.0-fac)*ps->orig->ofsy); + done= 1; + } + } + } + + /* copy locations to tabs */ + for(pa= ar->panels.first; pa; pa= pa->next) + if(pa->paneltab && (pa->runtime_flag & PNL_ACTIVE)) + ui_panel_copy_offset(pa, pa->paneltab); + + /* free panelsort array */ + for(ps= panelsort, a=0; a<tot; a++, ps++) + MEM_freeN(ps->pa); + MEM_freeN(panelsort); + + return done; +} + + +static void ui_do_animate(const bContext *C, Panel *panel) +{ + uiHandlePanelData *data= panel->activedata; + ScrArea *sa= CTX_wm_area(C); + ARegion *ar= CTX_wm_region(C); + float fac; + + fac= (PIL_check_seconds_timer()-data->starttime)/ANIMATION_TIME; + fac= sqrt(fac); + fac= MIN2(fac, 1.0f); + + /* for max 1 second, interpolate positions */ + if(uiAlignPanelStep(sa, ar, fac, 0)) + ED_region_tag_redraw(ar); + else + fac= 1.0f; + + if(fac >= 1.0f) { + panel_activate_state(C, panel, PANEL_STATE_EXIT); + return; + } +} + +void uiBeginPanels(const bContext *C, ARegion *ar) +{ + Panel *pa; + + /* set all panels as inactive, so that at the end we know + * which ones were used */ + for(pa=ar->panels.first; pa; pa=pa->next) { + if(pa->runtime_flag & PNL_ACTIVE) + pa->runtime_flag= PNL_WAS_ACTIVE; + else + pa->runtime_flag= 0; + } +} + +/* only draws blocks with panels */ +void uiEndPanels(const bContext *C, ARegion *ar) +{ + ScrArea *sa= CTX_wm_area(C); + uiBlock *block; + Panel *panot, *panew, *patest, *pa; + + /* offset contents */ + for(block= ar->uiblocks.first; block; block= block->next) + if(block->active && block->panel) + ui_offset_panel_block(block); + + /* consistancy; are panels not made, whilst they have tabs */ + for(panot= ar->panels.first; panot; panot= panot->next) { + if((panot->runtime_flag & PNL_ACTIVE)==0) { // not made + + for(panew= ar->panels.first; panew; panew= panew->next) { + if((panew->runtime_flag & PNL_ACTIVE)) { + if(panew->paneltab==panot) { // panew is tab in notmade pa + break; + } + } + } + /* now panew can become the new parent, check all other tabs */ + if(panew) { + for(patest= ar->panels.first; patest; patest= patest->next) { + if(patest->paneltab == panot) { + patest->paneltab= panew; + } + } + panot->paneltab= panew; + panew->paneltab= NULL; + ED_region_tag_redraw(ar); // the buttons panew were not made + } + } + } + + /* re-align, possibly with animation */ + if(panels_re_align(sa, ar, &pa)) { + if(pa) + panel_activate_state(C, pa, PANEL_STATE_ANIMATION); + else + uiAlignPanelStep(sa, ar, 1.0, 0); + } + + /* draw panels, selected on top */ + for(block= ar->uiblocks.first; block; block=block->next) { + if(block->active && block->panel && !(block->panel->flag & PNL_SELECT)) { + uiDrawBlock(C, block); + } + } + + for(block= ar->uiblocks.first; block; block=block->next) { + if(block->active && block->panel && (block->panel->flag & PNL_SELECT)) { + uiDrawBlock(C, block); + } + } +} + +/* ------------ panel merging ---------------- */ + +static void check_panel_overlap(ARegion *ar, Panel *panel) +{ + Panel *pa; + + /* also called with panel==NULL for clear */ + + for(pa=ar->panels.first; pa; pa=pa->next) { + pa->flag &= ~PNL_OVERLAP; + if(panel && (pa != panel)) { + if(pa->paneltab==NULL && (pa->runtime_flag & PNL_ACTIVE)) { + float safex= 0.2, safey= 0.2; + + if(pa->flag & PNL_CLOSEDX) safex= 0.05; + else if(pa->flag & PNL_CLOSEDY) safey= 0.05; + else if(panel->flag & PNL_CLOSEDX) safex= 0.05; + else if(panel->flag & PNL_CLOSEDY) safey= 0.05; + + if(pa->ofsx > panel->ofsx- safex*panel->sizex) + if(pa->ofsx+pa->sizex < panel->ofsx+ (1.0+safex)*panel->sizex) + if(pa->ofsy > panel->ofsy- safey*panel->sizey) + if(pa->ofsy+pa->sizey < panel->ofsy+ (1.0+safey)*panel->sizey) + pa->flag |= PNL_OVERLAP; + } + } + } +} + +static void test_add_new_tabs(ARegion *ar) +{ + Panel *pa, *pasel=NULL, *palap=NULL; + /* search selected and overlapped panel */ + + pa= ar->panels.first; + while(pa) { + if(pa->runtime_flag & PNL_ACTIVE) { + if(pa->flag & PNL_SELECT) pasel= pa; + if(pa->flag & PNL_OVERLAP) palap= pa; + } + pa= pa->next; + } + + if(pasel && palap==NULL) { + + /* copy locations */ + pa= ar->panels.first; + while(pa) { + if(pa->paneltab==pasel) { + ui_panel_copy_offset(pa, pasel); + } + pa= pa->next; + } + } + + if(pasel==NULL || palap==NULL) return; + + /* the overlapped panel becomes a tab */ + palap->paneltab= pasel; + + /* the selected panel gets coords of overlapped one */ + ui_panel_copy_offset(pasel, palap); + + /* and its tabs */ + pa= ar->panels.first; + while(pa) { + if(pa->paneltab == pasel) { + ui_panel_copy_offset(pa, palap); + } + pa= pa->next; + } + + /* but, the overlapped panel already can have tabs too! */ + pa= ar->panels.first; + while(pa) { + if(pa->paneltab == palap) { + pa->paneltab = pasel; + } + pa= pa->next; + } +} + +/************************ panel dragging ****************************/ + +static void ui_do_drag(const bContext *C, wmEvent *event, Panel *panel) +{ + uiHandlePanelData *data= panel->activedata; + ScrArea *sa= CTX_wm_area(C); + ARegion *ar= CTX_wm_region(C); + short align= panel_aligned(sa, ar), dx=0, dy=0; + + /* first clip for window, no dragging outside */ + if(!BLI_in_rcti(&ar->winrct, event->x, event->y)) + return; + + dx= (event->x-data->startx) & ~(PNL_GRID-1); + dy= (event->y-data->starty) & ~(PNL_GRID-1); + + if(data->state == PANEL_STATE_DRAG_SCALE) { + panel->sizex = MAX2(data->startsizex+dx, UI_PANEL_MINX); + + if(data->startsizey-dy < UI_PANEL_MINY) + dy= -UI_PANEL_MINY+data->startsizey; + + panel->sizey= data->startsizey-dy; + panel->ofsy= data->startofsy+dy; + } + else { + /* reset the panel snapping, to allow dragging away from snapped edges */ + panel->snap = PNL_SNAP_NONE; + + panel->ofsx = data->startofsx+dx; + panel->ofsy = data->startofsy+dy; + check_panel_overlap(ar, panel); + + if(align) uiAlignPanelStep(sa, ar, 0.2, 1); + } + + ED_region_tag_redraw(ar); +} + +static void ui_do_untab(const bContext *C, wmEvent *event, Panel *panel) +{ + uiHandlePanelData *data= panel->activedata; + ARegion *ar= CTX_wm_region(C); + Panel *pa, *panew= NULL; + int nr; + + /* wait until a threshold is passed to untab */ + if(abs(event->x-data->startx) + abs(event->y-data->starty) > 6) { + /* find new parent panel */ + nr= 0; + for(pa= ar->panels.first; pa; pa=pa->next) { + if(pa->paneltab==panel) { + panew= pa; + nr++; + } + } + + /* make old tabs point to panew */ + panew->paneltab= NULL; + + for(pa= ar->panels.first; pa; pa=pa->next) + if(pa->paneltab==panel) + pa->paneltab= panew; + + panel_activate_state(C, panel, PANEL_STATE_DRAG); + } +} + +/******************* region level panel interaction *****************/ + +static void panel_clicked_tabs(const bContext *C, ScrArea *sa, ARegion *ar, uiBlock *block, int mousex) +{ + Panel *pa, *tabsel=NULL, *panel= block->panel; + int nr= 1, a, width, ofsx; + + ofsx= PNL_ICON; + if(block->panel->type && (block->panel->control & UI_PNL_CLOSE)) ofsx+= PNL_ICON; + + /* count */ + for(pa= ar->panels.first; pa; pa=pa->next) + if(pa!=panel) + if((pa->runtime_flag & PNL_ACTIVE) && pa->paneltab==panel) + nr++; + + if(nr==1) return; + + /* find clicked tab, mouse in panel coords */ + a= 0; + width= (int)((float)(panel->sizex - ofsx-10)/nr); + pa= ar->panels.first; + while(pa) { + if(pa==panel || ((pa->runtime_flag & PNL_ACTIVE) && pa->paneltab==panel)) { + if((mousex > ofsx+a*width) && (mousex < ofsx+(a+1)*width)) { + tabsel= pa; + break; + } + a++; + } + pa= pa->next; + } + + if(tabsel) { + if(tabsel == panel) { + panel_activate_state(C, panel, PANEL_STATE_WAIT_UNTAB); + } + else { + /* tabsel now becomes parent for all others */ + panel->paneltab= tabsel; + tabsel->paneltab= NULL; + + pa= ar->panels.first; + while(pa) { + if(pa->paneltab == panel) pa->paneltab = tabsel; + pa= pa->next; + } + + /* copy locations to tabs */ + for(pa= ar->panels.first; pa; pa= pa->next) { + if(pa->paneltab && pa->runtime_flag & PNL_ACTIVE) { + ui_panel_copy_offset(pa, pa->paneltab); + } + } + + /* panels now differ size.. */ + if(panel_aligned(sa, ar)) + panel_activate_state(C, tabsel, PANEL_STATE_ANIMATION); + + ED_region_tag_redraw(ar); + } + } + +} + +/* this function is supposed to call general window drawing too */ +/* also it supposes a block has panel, and isnt a menu */ +static void ui_handle_panel_header(const bContext *C, uiBlock *block, int mx, int my) +{ + ScrArea *sa= CTX_wm_area(C); + ARegion *ar= CTX_wm_region(C); + Panel *pa; + int align= panel_aligned(sa, ar), button= 0; + + /* mouse coordinates in panel space! */ + + /* XXX weak code, currently it assumes layout style for location of widgets */ + + /* check open/collapsed button */ + if(block->panel->flag & PNL_CLOSEDX) { + if(my >= block->maxy) button= 1; + } + else if(block->panel->control & UI_PNL_CLOSE) { + if(mx <= block->minx+10+PNL_ICON-2) button= 2; + else if(mx <= block->minx+10+2*PNL_ICON+2) button= 1; + } + else if(mx <= block->minx+10+PNL_ICON+2) { + button= 1; + } + + if(button) { + if(button==2) { // close + ED_region_tag_redraw(ar); + } + else { // collapse + if(block->panel->flag & PNL_CLOSED) { + block->panel->flag &= ~PNL_CLOSED; + /* snap back up so full panel aligns with screen edge */ + if (block->panel->snap & PNL_SNAP_BOTTOM) + block->panel->ofsy= 0; + } + else if(align==BUT_HORIZONTAL) { + block->panel->flag |= PNL_CLOSEDX; + } + else { + /* snap down to bottom screen edge*/ + block->panel->flag |= PNL_CLOSEDY; + if (block->panel->snap & PNL_SNAP_BOTTOM) + block->panel->ofsy= -block->panel->sizey; + } + + for(pa= ar->panels.first; pa; pa= pa->next) { + if(pa->paneltab==block->panel) { + if(block->panel->flag & PNL_CLOSED) pa->flag |= PNL_CLOSED; + else pa->flag &= ~PNL_CLOSED; + } + } + } + + if(align) + panel_activate_state(C, block->panel, PANEL_STATE_ANIMATION); + else + ED_region_tag_redraw(ar); + } + else if(block->panel->flag & PNL_CLOSED) { + panel_activate_state(C, block->panel, PANEL_STATE_DRAG); + } + /* check if clicked in tabbed area */ + else if(mx < block->maxx-PNL_ICON-3 && panel_has_tabs(ar, block->panel)) { + panel_clicked_tabs(C, sa, ar, block, mx); + } + else { + panel_activate_state(C, block->panel, PANEL_STATE_DRAG); + } +} + +int ui_handler_panel_region(bContext *C, wmEvent *event) +{ + ARegion *ar= CTX_wm_region(C); + uiBlock *block; + int retval, mx, my, inside_header= 0, inside_scale= 0, inside; + + retval= WM_UI_HANDLER_CONTINUE; + + /* buttons get priority */ + if(ui_button_is_active(ar)) + return retval; + + for(block=ar->uiblocks.last; block; block=block->prev) { + mx= event->x; + my= event->y; + ui_window_to_block(ar, block, &mx, &my); + + /* check if inside boundbox */ + inside= 0; + + if(block->panel && block->panel->paneltab==NULL) + if(block->minx <= mx && block->maxx >= mx) + if(block->miny <= my && block->maxy+PNL_HEADER >= my) + inside= 1; + + if(inside) { + /* clicked at panel header? */ + if(block->panel->flag & PNL_CLOSEDX) { + if(block->minx <= mx && block->minx+PNL_HEADER >= mx) + inside_header= 1; + } + else if((block->maxy <= my) && (block->maxy+PNL_HEADER >= my)) { + inside_header= 1; + } + else if(block->panel->control & UI_PNL_SCALE) { + if(block->maxx-PNL_HEADER <= mx) + if(block->miny+PNL_HEADER >= my) + inside_scale= 1; + } + + if(event->val==KM_PRESS) { + if(event->type == LEFTMOUSE) { + if(inside_header) { + ui_handle_panel_header(C, block, mx, my); + break; + } + else if(inside_scale && !(block->panel->flag & PNL_CLOSED)) { + panel_activate_state(C, block->panel, PANEL_STATE_DRAG_SCALE); + break; + } + } + else if(event->type == ESCKEY) { + /*XXX 2.50 if(block->handler) { + rem_blockhandler(sa, block->handler); + ED_region_tag_redraw(ar); + retval= WM_UI_HANDLER_BREAK; + }*/ + } + else if(event->type==PADPLUSKEY || event->type==PADMINUS) { + int zoom=0; + + /* if panel is closed, only zoom if mouse is over the header */ + if (block->panel->flag & (PNL_CLOSEDX|PNL_CLOSEDY)) { + if (inside_header) + zoom=1; + } + else + zoom=1; + +#if 0 // XXX make float panel exception? + if(zoom) { + ScrArea *sa= CTX_wm_area(C); + SpaceLink *sl= sa->spacedata.first; + + if(sa->spacetype!=SPACE_BUTS) { + if(!(block->panel->control & UI_PNL_SCALE)) { + if(event->type==PADPLUSKEY) sl->blockscale+= 0.1; + else sl->blockscale-= 0.1; + CLAMP(sl->blockscale, 0.6, 1.0); + + ED_region_tag_redraw(ar); + retval= WM_UI_HANDLER_BREAK; + } + } + } +#endif + } + } + } + } + + return retval; +} + +/**************** window level modal panel interaction **************/ + +static int ui_handler_panel(bContext *C, wmEvent *event, void *userdata) +{ + Panel *panel= userdata; + uiHandlePanelData *data= panel->activedata; + + /* verify if we can stop */ + if(event->type == LEFTMOUSE && event->val!=KM_PRESS) { + ScrArea *sa= CTX_wm_area(C); + ARegion *ar= CTX_wm_region(C); + int align= panel_aligned(sa, ar); + + if(align) + panel_activate_state(C, panel, PANEL_STATE_ANIMATION); + else + panel_activate_state(C, panel, PANEL_STATE_EXIT); + + return WM_UI_HANDLER_BREAK; + } + else if(event->type == MOUSEMOVE) { + if(data->state == PANEL_STATE_WAIT_UNTAB) + ui_do_untab(C, event, panel); + else if(data->state == PANEL_STATE_DRAG) + ui_do_drag(C, event, panel); + } + else if(event->type == TIMER && event->customdata == data->animtimer) { + if(data->state == PANEL_STATE_ANIMATION) + ui_do_animate(C, panel); + else if(data->state == PANEL_STATE_DRAG) + ui_do_drag(C, event, panel); + } + + data= panel->activedata; + + if(data && data->state == PANEL_STATE_ANIMATION) + return WM_UI_HANDLER_CONTINUE; + else + return WM_UI_HANDLER_BREAK; +} + +static void ui_handler_remove_panel(bContext *C, void *userdata) +{ + Panel *pa= userdata; + + panel_activate_state(C, pa, PANEL_STATE_EXIT); +} + +static void panel_activate_state(const bContext *C, Panel *pa, uiHandlePanelState state) +{ + uiHandlePanelData *data= pa->activedata; + wmWindow *win= CTX_wm_window(C); + ARegion *ar= CTX_wm_region(C); + + if(data && data->state == state) + return; + + if(state == PANEL_STATE_EXIT || state == PANEL_STATE_ANIMATION) { + if(data && data->state != PANEL_STATE_ANIMATION) { + test_add_new_tabs(ar); // also copies locations of tabs in dragged panel + check_panel_overlap(ar, NULL); // clears + } + + pa->flag &= ~PNL_SELECT; + } + else + pa->flag |= PNL_SELECT; + + if(data && data->animtimer) { + WM_event_remove_window_timer(win, data->animtimer); + data->animtimer= NULL; + } + + if(state == PANEL_STATE_EXIT) { + MEM_freeN(data); + pa->activedata= NULL; + + WM_event_remove_ui_handler(&win->handlers, ui_handler_panel, ui_handler_remove_panel, pa); + } + else { + if(!data) { + data= MEM_callocN(sizeof(uiHandlePanelData), "uiHandlePanelData"); + pa->activedata= data; + + WM_event_add_ui_handler(C, &win->handlers, ui_handler_panel, ui_handler_remove_panel, pa); + } + + if(ELEM(state, PANEL_STATE_ANIMATION, PANEL_STATE_DRAG)) + data->animtimer= WM_event_add_window_timer(win, TIMER, ANIMATION_INTERVAL); + + data->state= state; + data->startx= win->eventstate->x; + data->starty= win->eventstate->y; + data->startofsx= pa->ofsx; + data->startofsy= pa->ofsy; + data->startsizex= pa->sizex; + data->startsizey= pa->sizey; + data->starttime= PIL_check_seconds_timer(); + } + + ED_region_tag_redraw(ar); + + /* XXX exception handling, 3d window preview panel */ + /* if(block->drawextra==BIF_view3d_previewdraw) + BIF_view3d_previewrender_clear(curarea);*/ + + /* XXX exception handling, 3d window preview panel */ + /* if(block->drawextra==BIF_view3d_previewdraw) + BIF_view3d_previewrender_signal(curarea, PR_DISPRECT); + else if(strcmp(block->name, "image_panel_preview")==0) + image_preview_event(2); */ +} + diff --git a/source/blender/editors/interface/interface_regions.c b/source/blender/editors/interface/interface_regions.c new file mode 100644 index 00000000000..222c3fe892d --- /dev/null +++ b/source/blender/editors/interface/interface_regions.c @@ -0,0 +1,2492 @@ +/** + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2008 Blender Foundation. + * All rights reserved. + * + * Contributor(s): Blender Foundation + * + * ***** END GPL LICENSE BLOCK ***** + */ + + +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> + +#include "MEM_guardedalloc.h" + +#include "DNA_screen_types.h" +#include "DNA_view2d_types.h" +#include "DNA_userdef_types.h" +#include "DNA_windowmanager_types.h" + +#include "BLI_arithb.h" +#include "BLI_blenlib.h" +#include "BLI_dynstr.h" + +#include "BKE_context.h" +#include "BKE_report.h" +#include "BKE_screen.h" +#include "BKE_texture.h" +#include "BKE_utildefines.h" + +#include "WM_api.h" +#include "WM_types.h" +#include "wm_draw.h" +#include "wm_subwindow.h" +#include "wm_window.h" + +#include "RNA_access.h" + +#include "BIF_gl.h" + +#include "UI_interface.h" +#include "UI_interface_icons.h" +#include "UI_view2d.h" + +#include "BLF_api.h" + +#include "ED_screen.h" + +#include "interface_intern.h" + +#define MENU_BUTTON_HEIGHT 20 +#define MENU_SEPR_HEIGHT 6 +#define B_NOP -1 +#define MENU_SHADOW_SIDE 8 +#define MENU_SHADOW_BOTTOM 10 +#define MENU_TOP 8 + +/*********************** Menu Data Parsing ********************* */ + +typedef struct { + char *str; + int retval; + int icon; +} MenuEntry; + +typedef struct { + char *instr; + char *title; + int titleicon; + + MenuEntry *items; + int nitems, itemssize; +} MenuData; + +static MenuData *menudata_new(char *instr) +{ + MenuData *md= MEM_mallocN(sizeof(*md), "MenuData"); + + md->instr= instr; + md->title= NULL; + md->titleicon= 0; + md->items= NULL; + md->nitems= md->itemssize= 0; + + return md; +} + +static void menudata_set_title(MenuData *md, char *title, int titleicon) +{ + if (!md->title) + md->title= title; + if (!md->titleicon) + md->titleicon= titleicon; +} + +static void menudata_add_item(MenuData *md, char *str, int retval, int icon) +{ + if (md->nitems==md->itemssize) { + int nsize= md->itemssize?(md->itemssize<<1):1; + MenuEntry *oitems= md->items; + + md->items= MEM_mallocN(nsize*sizeof(*md->items), "md->items"); + if (oitems) { + memcpy(md->items, oitems, md->nitems*sizeof(*md->items)); + MEM_freeN(oitems); + } + + md->itemssize= nsize; + } + + md->items[md->nitems].str= str; + md->items[md->nitems].retval= retval; + md->items[md->nitems].icon= icon; + md->nitems++; +} + +void menudata_free(MenuData *md) +{ + MEM_freeN(md->instr); + if (md->items) + MEM_freeN(md->items); + MEM_freeN(md); +} + + /** + * Parse menu description strings, string is of the + * form "[sss%t|]{(sss[%xNN]|), (%l|)}", ssss%t indicates the + * menu title, sss or sss%xNN indicates an option, + * if %xNN is given then NN is the return value if + * that option is selected otherwise the return value + * is the index of the option (starting with 1). %l + * indicates a seperator. + * + * @param str String to be parsed. + * @retval new menudata structure, free with menudata_free() + */ +MenuData *decompose_menu_string(char *str) +{ + char *instr= BLI_strdup(str); + MenuData *md= menudata_new(instr); + char *nitem= NULL, *s= instr; + int nicon=0, nretval= 1, nitem_is_title= 0; + + while (1) { + char c= *s; + + if (c=='%') { + if (s[1]=='x') { + nretval= atoi(s+2); + + *s= '\0'; + s++; + } else if (s[1]=='t') { + nitem_is_title= 1; + + *s= '\0'; + s++; + } else if (s[1]=='l') { + nitem= "%l"; + s++; + } else if (s[1]=='i') { + nicon= atoi(s+2); + + *s= '\0'; + s++; + } + } else if (c=='|' || c=='\0') { + if (nitem) { + *s= '\0'; + + if (nitem_is_title) { + menudata_set_title(md, nitem, nicon); + nitem_is_title= 0; + } else { + /* prevent separator to get a value */ + if(nitem[0]=='%' && nitem[1]=='l') + menudata_add_item(md, nitem, -1, nicon); + else + menudata_add_item(md, nitem, nretval, nicon); + nretval= md->nitems+1; + } + + nitem= NULL; + nicon= 0; + } + + if (c=='\0') + break; + } else if (!nitem) + nitem= s; + + s++; + } + + return md; +} + +void ui_set_name_menu(uiBut *but, int value) +{ + MenuData *md; + int i; + + md= decompose_menu_string(but->str); + for (i=0; i<md->nitems; i++) + if (md->items[i].retval==value) + strcpy(but->drawstr, md->items[i].str); + + menudata_free(md); +} + +int ui_step_name_menu(uiBut *but, int step) +{ + MenuData *md; + int value= ui_get_but_val(but); + int i; + + md= decompose_menu_string(but->str); + for (i=0; i<md->nitems; i++) + if (md->items[i].retval==value) + break; + + if(step==1) { + /* skip separators */ + for(; i<md->nitems-1; i++) { + if(md->items[i+1].retval != -1) { + value= md->items[i+1].retval; + break; + } + } + } + else { + if(i>0) { + /* skip separators */ + for(; i>0; i--) { + if(md->items[i-1].retval != -1) { + value= md->items[i-1].retval; + break; + } + } + } + } + + menudata_free(md); + + return value; +} + + +/******************** Creating Temporary regions ******************/ + +ARegion *ui_add_temporary_region(bScreen *sc) +{ + ARegion *ar; + + ar= MEM_callocN(sizeof(ARegion), "area region"); + BLI_addtail(&sc->regionbase, ar); + + ar->regiontype= RGN_TYPE_TEMPORARY; + ar->alignment= RGN_ALIGN_FLOAT; + + return ar; +} + +void ui_remove_temporary_region(bContext *C, bScreen *sc, ARegion *ar) +{ + if(CTX_wm_window(C)) + wm_draw_region_clear(CTX_wm_window(C), ar); + + ED_region_exit(C, ar); + BKE_area_region_free(NULL, ar); /* NULL: no spacetype */ + BLI_freelinkN(&sc->regionbase, ar); +} + +/************************* Creating Tooltips **********************/ + +typedef struct uiTooltipData { + rcti bbox; + uiFontStyle fstyle; + char *tip; +} uiTooltipData; + +static void ui_tooltip_region_draw(const bContext *C, ARegion *ar) +{ + uiTooltipData *data= ar->regiondata; + + ui_draw_menu_back(U.uistyles.first, NULL, &data->bbox); + + /* draw text */ + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + uiStyleFontSet(&data->fstyle); + uiStyleFontDraw(&data->fstyle, &data->bbox, data->tip); +} + +static void ui_tooltip_region_free(ARegion *ar) +{ + uiTooltipData *data; + + data= ar->regiondata; + MEM_freeN(data->tip); + MEM_freeN(data); + ar->regiondata= NULL; +} + +ARegion *ui_tooltip_create(bContext *C, ARegion *butregion, uiBut *but) +{ + uiStyle *style= U.uistyles.first; // XXX pass on as arg + static ARegionType type; + ARegion *ar; + uiTooltipData *data; + float fonth, fontw, aspect= but->block->aspect; + float x1f, x2f, y1f, y2f; + int x1, x2, y1, y2, winx, winy, ofsx, ofsy; + + if(!but->tip || strlen(but->tip)==0) + return NULL; + + /* create area region */ + ar= ui_add_temporary_region(CTX_wm_screen(C)); + + memset(&type, 0, sizeof(ARegionType)); + type.draw= ui_tooltip_region_draw; + type.free= ui_tooltip_region_free; + ar->type= &type; + + /* create tooltip data */ + data= MEM_callocN(sizeof(uiTooltipData), "uiTooltipData"); + data->tip= BLI_strdup(but->tip); + + /* set font, get bb */ + data->fstyle= style->widget; /* copy struct */ + data->fstyle.align= UI_STYLE_TEXT_CENTER; + ui_fontscale(&data->fstyle.points, aspect); + uiStyleFontSet(&data->fstyle); + fontw= aspect * BLF_width(data->tip); + fonth= aspect * BLF_height(data->tip); + + ar->regiondata= data; + + /* compute position */ + ofsx= (but->block->panel)? but->block->panel->ofsx: 0; + ofsy= (but->block->panel)? but->block->panel->ofsy: 0; + + x1f= (but->x1+but->x2)/2.0f + ofsx - 16.0f*aspect; + x2f= x1f + fontw + 16.0f*aspect; + y2f= but->y1 + ofsy - 15.0f*aspect; + y1f= y2f - fonth - 10.0f*aspect; + + /* copy to int, gets projected if possible too */ + x1= x1f; y1= y1f; x2= x2f; y2= y2f; + + if(butregion) { + /* XXX temp, region v2ds can be empty still */ + if(butregion->v2d.cur.xmin != butregion->v2d.cur.xmax) { + UI_view2d_to_region_no_clip(&butregion->v2d, x1f, y1f, &x1, &y1); + UI_view2d_to_region_no_clip(&butregion->v2d, x2f, y2f, &x2, &y2); + } + + x1 += butregion->winrct.xmin; + x2 += butregion->winrct.xmin; + y1 += butregion->winrct.ymin; + y2 += butregion->winrct.ymin; + } + + wm_window_get_size(CTX_wm_window(C), &winx, &winy); + + if(x2 > winx) { + /* super size */ + if(x2 > winx + x1) { + x2= winx; + x1= 0; + } + else { + x1 -= x2-winx; + x2= winx; + } + } + if(y1 < 0) { + y1 += 56*aspect; + y2 += 56*aspect; + } + + /* widget rect, in region coords */ + data->bbox.xmin= MENU_SHADOW_SIDE; + data->bbox.xmax= x2-x1 + MENU_SHADOW_SIDE; + data->bbox.ymin= MENU_SHADOW_BOTTOM; + data->bbox.ymax= y2-y1 + MENU_SHADOW_BOTTOM; + + /* region bigger for shadow */ + ar->winrct.xmin= x1 - MENU_SHADOW_SIDE; + ar->winrct.xmax= x2 + MENU_SHADOW_SIDE; + ar->winrct.ymin= y1 - MENU_SHADOW_BOTTOM; + ar->winrct.ymax= y2 + MENU_TOP; + + /* adds subwindow */ + ED_region_init(C, ar); + + /* notify change and redraw */ + ED_region_tag_redraw(ar); + + return ar; +} + +void ui_tooltip_free(bContext *C, ARegion *ar) +{ + ui_remove_temporary_region(C, CTX_wm_screen(C), ar); +} + + +/************************* Creating Search Box **********************/ + +struct uiSearchItems { + int maxitem, totitem, maxstrlen; + + int offset, offset_i; /* offset for inserting in array */ + int more; /* flag indicating there are more items */ + + char **names; + void **pointers; + +}; + +typedef struct uiSearchboxData { + rcti bbox; + uiFontStyle fstyle; + uiSearchItems items; + int active; /* index in items array */ + int noback; /* when menu opened with enough space for this */ +} uiSearchboxData; + +#define SEARCH_ITEMS 10 + +/* exported for use by search callbacks */ +/* returns zero if nothing to add */ +int uiSearchItemAdd(uiSearchItems *items, const char *name, void *poin) +{ + + if(items->totitem>=items->maxitem) { + items->more= 1; + return 0; + } + + /* skip first items in list */ + if(items->offset_i > 0) { + items->offset_i--; + return 1; + } + + BLI_strncpy(items->names[items->totitem], name, items->maxstrlen); + items->pointers[items->totitem]= poin; + + items->totitem++; + + return 1; +} + +int uiSearchBoxhHeight(void) +{ + return SEARCH_ITEMS*MENU_BUTTON_HEIGHT + 2*MENU_TOP; +} + +/* ar is the search box itself */ +static void ui_searchbox_select(bContext *C, ARegion *ar, uiBut *but, int step) +{ + uiSearchboxData *data= ar->regiondata; + + /* apply step */ + data->active+= step; + + if(data->items.totitem==0) + data->active= 0; + else if(data->active > data->items.totitem) { + if(data->items.more) { + data->items.offset++; + data->active= data->items.totitem; + ui_searchbox_update(C, ar, but, 0); + } + else + data->active= data->items.totitem; + } + else if(data->active < 1) { + if(data->items.offset) { + data->items.offset--; + data->active= 1; + ui_searchbox_update(C, ar, but, 0); + } + else if(data->active < 0) + data->active= 0; + } + + ED_region_tag_redraw(ar); +} + +static void ui_searchbox_butrect(rcti *rect, uiSearchboxData *data, int itemnr) +{ + int buth= (data->bbox.ymax-data->bbox.ymin - 2*MENU_TOP)/SEARCH_ITEMS; + + *rect= data->bbox; + rect->xmin= data->bbox.xmin + 3.0f; + rect->xmax= data->bbox.xmax - 3.0f; + + rect->ymax= data->bbox.ymax - MENU_TOP - itemnr*buth; + rect->ymin= rect->ymax - buth; + +} + +/* string validated to be of correct length (but->hardmax) */ +void ui_searchbox_apply(uiBut *but, ARegion *ar) +{ + uiSearchboxData *data= ar->regiondata; + + but->func_arg2= NULL; + + if(data->active) { + char *name= data->items.names[data->active-1]; + char *cpoin= strchr(name, '|'); + + if(cpoin) cpoin[0]= 0; + BLI_strncpy(but->editstr, name, data->items.maxstrlen); + if(cpoin) cpoin[0]= '|'; + + but->func_arg2= data->items.pointers[data->active-1]; + } +} + +void ui_searchbox_event(bContext *C, ARegion *ar, uiBut *but, wmEvent *event) +{ + uiSearchboxData *data= ar->regiondata; + + switch(event->type) { + case WHEELUPMOUSE: + case UPARROWKEY: + ui_searchbox_select(C, ar, but, -1); + break; + case WHEELDOWNMOUSE: + case DOWNARROWKEY: + ui_searchbox_select(C, ar, but, 1); + break; + case MOUSEMOVE: + if(BLI_in_rcti(&ar->winrct, event->x, event->y)) { + rcti rect; + int a; + + for(a=0; a<data->items.totitem; a++) { + ui_searchbox_butrect(&rect, data, a); + if(BLI_in_rcti(&rect, event->x - ar->winrct.xmin, event->y - ar->winrct.ymin)) { + if( data->active!= a+1) { + data->active= a+1; + ui_searchbox_select(C, ar, but, 0); + break; + } + } + } + } + break; + } +} + +/* ar is the search box itself */ +void ui_searchbox_update(bContext *C, ARegion *ar, uiBut *but, int reset) +{ + uiSearchboxData *data= ar->regiondata; + + /* reset vars */ + data->items.totitem= 0; + data->items.more= 0; + if(reset==0) + data->items.offset_i= data->items.offset; + else { + data->items.offset_i= data->items.offset= 0; + data->active= 0; + } + + /* callback */ + if(but->search_func) + but->search_func(C, but->search_arg, but->editstr, &data->items); + + if(reset) { + int a; + /* handle case where editstr is equal to one of items */ + for(a=0; a<data->items.totitem; a++) { + char *cpoin= strchr(data->items.names[a], '|'); + + if(cpoin) cpoin[0]= 0; + if(0==strcmp(but->editstr, data->items.names[a])) + data->active= a+1; + if(cpoin) cpoin[0]= '|'; + } + } + + /* validate selected item */ + ui_searchbox_select(C, ar, but, 0); + + ED_region_tag_redraw(ar); +} + +static void ui_searchbox_region_draw(const bContext *C, ARegion *ar) +{ + uiSearchboxData *data= ar->regiondata; + + /* pixel space */ + wmOrtho2(-0.01f, ar->winx-0.01f, -0.01f, ar->winy-0.01f); + + if(!data->noback) + ui_draw_search_back(U.uistyles.first, NULL, &data->bbox); + + /* draw text */ + if(data->items.totitem) { + rcti rect; + int a; + + /* draw items */ + for(a=0; a<data->items.totitem; a++) { + ui_searchbox_butrect(&rect, data, a); + + ui_draw_menu_item(&data->fstyle, &rect, data->items.names[a], (a+1)==data->active?UI_ACTIVE:0); + + } + /* indicate more */ + if(data->items.more) { + glEnable(GL_BLEND); + UI_icon_draw((data->bbox.xmax-data->bbox.xmin)/2, 8, ICON_TRIA_DOWN); + glDisable(GL_BLEND); + } + if(data->items.offset) { + glEnable(GL_BLEND); + UI_icon_draw((data->bbox.xmax-data->bbox.xmin)/2, data->bbox.ymax-13, ICON_TRIA_UP); + glDisable(GL_BLEND); + } + } +} + +static void ui_searchbox_region_free(ARegion *ar) +{ + uiSearchboxData *data= ar->regiondata; + int a; + + /* free search data */ + for(a=0; a<SEARCH_ITEMS; a++) + MEM_freeN(data->items.names[a]); + MEM_freeN(data->items.names); + MEM_freeN(data->items.pointers); + + MEM_freeN(data); + ar->regiondata= NULL; +} + +ARegion *ui_searchbox_create(bContext *C, ARegion *butregion, uiBut *but) +{ + uiStyle *style= U.uistyles.first; // XXX pass on as arg + static ARegionType type; + ARegion *ar; + uiSearchboxData *data; + float aspect= but->block->aspect; + float x1f, x2f, y1f, y2f; + int x1, x2, y1, y2, winx, winy, ofsx, ofsy; + + /* create area region */ + ar= ui_add_temporary_region(CTX_wm_screen(C)); + + memset(&type, 0, sizeof(ARegionType)); + type.draw= ui_searchbox_region_draw; + type.free= ui_searchbox_region_free; + ar->type= &type; + + /* create searchbox data */ + data= MEM_callocN(sizeof(uiSearchboxData), "uiSearchboxData"); + + /* set font, get bb */ + data->fstyle= style->widget; /* copy struct */ + data->fstyle.align= UI_STYLE_TEXT_CENTER; + ui_fontscale(&data->fstyle.points, aspect); + uiStyleFontSet(&data->fstyle); + + ar->regiondata= data; + + /* special case, hardcoded feature, not draw backdrop when called from menus, + assume for design that popup already added it */ + if(but->block->flag & UI_BLOCK_LOOP) + data->noback= 1; + + /* compute position */ + ofsx= (but->block->panel)? but->block->panel->ofsx: 0; + ofsy= (but->block->panel)? but->block->panel->ofsy: 0; + + x1f= but->x1 - 5; /* align text with button */ + x2f= but->x2 + 5; /* symmetrical */ + y2f= but->y1; + y1f= y2f - uiSearchBoxhHeight(); + + /* minimal width */ + if(x2f - x1f < 180) x2f= x1f+180; // XXX arbitrary + + /* copy to int, gets projected if possible too */ + x1= x1f; y1= y1f; x2= x2f; y2= y2f; + + if(butregion) { + if(butregion->v2d.cur.xmin != butregion->v2d.cur.xmax) { + UI_view2d_to_region_no_clip(&butregion->v2d, x1f, y1f, &x1, &y1); + UI_view2d_to_region_no_clip(&butregion->v2d, x2f, y2f, &x2, &y2); + } + + x1 += butregion->winrct.xmin; + x2 += butregion->winrct.xmin; + y1 += butregion->winrct.ymin; + y2 += butregion->winrct.ymin; + } + + wm_window_get_size(CTX_wm_window(C), &winx, &winy); + + if(x2 > winx) { + /* super size */ + if(x2 > winx + x1) { + x2= winx; + x1= 0; + } + else { + x1 -= x2-winx; + x2= winx; + } + } + if(y1 < 0) { + y1 += 36; + y2 += 36; + } + + /* widget rect, in region coords */ + data->bbox.xmin= MENU_SHADOW_SIDE; + data->bbox.xmax= x2-x1 + MENU_SHADOW_SIDE; + data->bbox.ymin= MENU_SHADOW_BOTTOM; + data->bbox.ymax= y2-y1 + MENU_SHADOW_BOTTOM; + + /* region bigger for shadow */ + ar->winrct.xmin= x1 - MENU_SHADOW_SIDE; + ar->winrct.xmax= x2 + MENU_SHADOW_SIDE; + ar->winrct.ymin= y1 - MENU_SHADOW_BOTTOM; + ar->winrct.ymax= y2; + + /* adds subwindow */ + ED_region_init(C, ar); + + /* notify change and redraw */ + ED_region_tag_redraw(ar); + + /* prepare search data */ + data->items.maxitem= SEARCH_ITEMS; + data->items.maxstrlen= but->hardmax; + data->items.totitem= 0; + data->items.names= MEM_callocN(SEARCH_ITEMS*sizeof(void *), "search names"); + data->items.pointers= MEM_callocN(SEARCH_ITEMS*sizeof(void *), "search pointers"); + for(x1=0; x1<SEARCH_ITEMS; x1++) + data->items.names[x1]= MEM_callocN(but->hardmax+1, "search pointers"); + + return ar; +} + +void ui_searchbox_free(bContext *C, ARegion *ar) +{ + ui_remove_temporary_region(C, CTX_wm_screen(C), ar); +} + + +/************************* Creating Menu Blocks **********************/ + +/* position block relative to but, result is in window space */ +static void ui_block_position(wmWindow *window, ARegion *butregion, uiBut *but, uiBlock *block) +{ + uiBut *bt; + uiSafetyRct *saferct; + rctf butrct; + float aspect; + int xsize, ysize, xof=0, yof=0, center; + short dir1= 0, dir2=0; + + /* transform to window coordinates, using the source button region/block */ + butrct.xmin= but->x1; butrct.xmax= but->x2; + butrct.ymin= but->y1; butrct.ymax= but->y2; + + ui_block_to_window_fl(butregion, but->block, &butrct.xmin, &butrct.ymin); + ui_block_to_window_fl(butregion, but->block, &butrct.xmax, &butrct.ymax); + + /* calc block rect */ + if(block->minx == 0.0f && block->maxx == 0.0f) { + if(block->buttons.first) { + block->minx= block->miny= 10000; + block->maxx= block->maxy= -10000; + + bt= block->buttons.first; + while(bt) { + if(bt->x1 < block->minx) block->minx= bt->x1; + if(bt->y1 < block->miny) block->miny= bt->y1; + + if(bt->x2 > block->maxx) block->maxx= bt->x2; + if(bt->y2 > block->maxy) block->maxy= bt->y2; + + bt= bt->next; + } + } + else { + /* we're nice and allow empty blocks too */ + block->minx= block->miny= 0; + block->maxx= block->maxy= 20; + } + } + + aspect= (float)(block->maxx - block->minx + 4); + ui_block_to_window_fl(butregion, but->block, &block->minx, &block->miny); + ui_block_to_window_fl(butregion, but->block, &block->maxx, &block->maxy); + + //block->minx-= 2.0; block->miny-= 2.0; + //block->maxx+= 2.0; block->maxy+= 2.0; + + xsize= block->maxx - block->minx+4; // 4 for shadow + ysize= block->maxy - block->miny+4; + aspect/= (float)xsize; + + if(but) { + int left=0, right=0, top=0, down=0; + int winx, winy; + + wm_window_get_size(window, &winx, &winy); + + if(block->direction & UI_CENTER) center= ysize/2; + else center= 0; + + if( butrct.xmin-xsize > 0.0) left= 1; + if( butrct.xmax+xsize < winx) right= 1; + if( butrct.ymin-ysize+center > 0.0) down= 1; + if( butrct.ymax+ysize-center < winy) top= 1; + + dir1= block->direction & UI_DIRECTION; + + /* secundary directions */ + if(dir1 & (UI_TOP|UI_DOWN)) { + if(dir1 & UI_LEFT) dir2= UI_LEFT; + else if(dir1 & UI_RIGHT) dir2= UI_RIGHT; + dir1 &= (UI_TOP|UI_DOWN); + } + + if(dir2==0) if(dir1==UI_LEFT || dir1==UI_RIGHT) dir2= UI_DOWN; + if(dir2==0) if(dir1==UI_TOP || dir1==UI_DOWN) dir2= UI_LEFT; + + /* no space at all? dont change */ + if(left || right) { + if(dir1==UI_LEFT && left==0) dir1= UI_RIGHT; + if(dir1==UI_RIGHT && right==0) dir1= UI_LEFT; + /* this is aligning, not append! */ + if(dir2==UI_LEFT && right==0) dir2= UI_RIGHT; + if(dir2==UI_RIGHT && left==0) dir2= UI_LEFT; + } + if(down || top) { + if(dir1==UI_TOP && top==0) dir1= UI_DOWN; + if(dir1==UI_DOWN && down==0) dir1= UI_TOP; + if(dir2==UI_TOP && top==0) dir2= UI_DOWN; + if(dir2==UI_DOWN && down==0) dir2= UI_TOP; + } + + if(dir1==UI_LEFT) { + xof= butrct.xmin - block->maxx; + if(dir2==UI_TOP) yof= butrct.ymin - block->miny-center; + else yof= butrct.ymax - block->maxy+center; + } + else if(dir1==UI_RIGHT) { + xof= butrct.xmax - block->minx; + if(dir2==UI_TOP) yof= butrct.ymin - block->miny-center; + else yof= butrct.ymax - block->maxy+center; + } + else if(dir1==UI_TOP) { + yof= butrct.ymax - block->miny; + if(dir2==UI_RIGHT) xof= butrct.xmax - block->maxx; + else xof= butrct.xmin - block->minx; + // changed direction? + if((dir1 & block->direction)==0) { + if(block->direction & UI_SHIFT_FLIPPED) + xof+= dir2==UI_LEFT?25:-25; + uiBlockFlipOrder(block); + } + } + else if(dir1==UI_DOWN) { + yof= butrct.ymin - block->maxy; + if(dir2==UI_RIGHT) xof= butrct.xmax - block->maxx; + else xof= butrct.xmin - block->minx; + // changed direction? + if((dir1 & block->direction)==0) { + if(block->direction & UI_SHIFT_FLIPPED) + xof+= dir2==UI_LEFT?25:-25; + uiBlockFlipOrder(block); + } + } + + /* and now we handle the exception; no space below or to top */ + if(top==0 && down==0) { + if(dir1==UI_LEFT || dir1==UI_RIGHT) { + // align with bottom of screen + yof= ysize; + } + } + + /* or no space left or right */ + if(left==0 && right==0) { + if(dir1==UI_TOP || dir1==UI_DOWN) { + // align with left size of screen + xof= -block->minx+5; + } + } + + // apply requested offset in the block + xof += block->xofs/block->aspect; + yof += block->yofs/block->aspect; + } + + /* apply */ + + for(bt= block->buttons.first; bt; bt= bt->next) { + ui_block_to_window_fl(butregion, but->block, &bt->x1, &bt->y1); + ui_block_to_window_fl(butregion, but->block, &bt->x2, &bt->y2); + + bt->x1 += xof; + bt->x2 += xof; + bt->y1 += yof; + bt->y2 += yof; + + bt->aspect= 1.0; + // ui_check_but recalculates drawstring size in pixels + ui_check_but(bt); + } + + block->minx += xof; + block->miny += yof; + block->maxx += xof; + block->maxy += yof; + + /* safety calculus */ + if(but) { + float midx= (butrct.xmin+butrct.xmax)/2.0; + float midy= (butrct.ymin+butrct.ymax)/2.0; + + /* when you are outside parent button, safety there should be smaller */ + + // parent button to left + if( midx < block->minx ) block->safety.xmin= block->minx-3; + else block->safety.xmin= block->minx-40; + // parent button to right + if( midx > block->maxx ) block->safety.xmax= block->maxx+3; + else block->safety.xmax= block->maxx+40; + + // parent button on bottom + if( midy < block->miny ) block->safety.ymin= block->miny-3; + else block->safety.ymin= block->miny-40; + // parent button on top + if( midy > block->maxy ) block->safety.ymax= block->maxy+3; + else block->safety.ymax= block->maxy+40; + + // exception for switched pulldowns... + if(dir1 && (dir1 & block->direction)==0) { + if(dir2==UI_RIGHT) block->safety.xmax= block->maxx+3; + if(dir2==UI_LEFT) block->safety.xmin= block->minx-3; + } + block->direction= dir1; + } + else { + block->safety.xmin= block->minx-40; + block->safety.ymin= block->miny-40; + block->safety.xmax= block->maxx+40; + block->safety.ymax= block->maxy+40; + } + + /* keep a list of these, needed for pulldown menus */ + saferct= MEM_callocN(sizeof(uiSafetyRct), "uiSafetyRct"); + saferct->parent= butrct; + saferct->safety= block->safety; + BLI_freelistN(&block->saferct); + if(but) + BLI_duplicatelist(&block->saferct, &but->block->saferct); + BLI_addhead(&block->saferct, saferct); +} + +static void ui_block_region_draw(const bContext *C, ARegion *ar) +{ + uiBlock *block; + + for(block=ar->uiblocks.first; block; block=block->next) + uiDrawBlock(C, block); +} + +uiPopupBlockHandle *ui_popup_block_create(bContext *C, ARegion *butregion, uiBut *but, uiBlockCreateFunc create_func, uiBlockHandleCreateFunc handle_create_func, void *arg) +{ + wmWindow *window= CTX_wm_window(C); + static ARegionType type; + ARegion *ar; + uiBlock *block; + uiBut *bt; + uiPopupBlockHandle *handle; + uiSafetyRct *saferct; + + /* create handle */ + handle= MEM_callocN(sizeof(uiPopupBlockHandle), "uiPopupBlockHandle"); + + /* store context for operator */ + handle->ctx_area= CTX_wm_area(C); + handle->ctx_region= CTX_wm_region(C); + + /* create area region */ + ar= ui_add_temporary_region(CTX_wm_screen(C)); + handle->region= ar; + + memset(&type, 0, sizeof(ARegionType)); + type.draw= ui_block_region_draw; + ar->type= &type; + + UI_add_region_handlers(&ar->handlers); + + /* create ui block */ + if(create_func) + block= create_func(C, handle->region, arg); + else + block= handle_create_func(C, handle, arg); + + if(block->handle) { + memcpy(block->handle, handle, sizeof(uiPopupBlockHandle)); + MEM_freeN(handle); + handle= block->handle; + } + else + block->handle= handle; + + ar->regiondata= handle; + + if(!block->endblock) + uiEndBlock(C, block); + + /* if this is being created from a button */ + if(but) { + if(ELEM(but->type, BLOCK, PULLDOWN)) + block->xofs = -2; /* for proper alignment */ + + /* only used for automatic toolbox, so can set the shift flag */ + if(but->flag & UI_MAKE_TOP) { + block->direction= UI_TOP|UI_SHIFT_FLIPPED; + uiBlockFlipOrder(block); + } + if(but->flag & UI_MAKE_DOWN) block->direction= UI_DOWN|UI_SHIFT_FLIPPED; + if(but->flag & UI_MAKE_LEFT) block->direction |= UI_LEFT; + if(but->flag & UI_MAKE_RIGHT) block->direction |= UI_RIGHT; + + ui_block_position(window, butregion, but, block); + } + else { + /* keep a list of these, needed for pulldown menus */ + saferct= MEM_callocN(sizeof(uiSafetyRct), "uiSafetyRct"); + saferct->safety= block->safety; + BLI_addhead(&block->saferct, saferct); + block->flag |= UI_BLOCK_POPUP; + } + + /* the block and buttons were positioned in window space as in 2.4x, now + * these menu blocks are regions so we bring it back to region space. + * additionally we add some padding for the menu shadow or rounded menus */ + ar->winrct.xmin= block->minx - MENU_SHADOW_SIDE; + ar->winrct.xmax= block->maxx + MENU_SHADOW_SIDE; + ar->winrct.ymin= block->miny - MENU_SHADOW_BOTTOM; + ar->winrct.ymax= block->maxy + MENU_TOP; + + block->minx -= ar->winrct.xmin; + block->maxx -= ar->winrct.xmin; + block->miny -= ar->winrct.ymin; + block->maxy -= ar->winrct.ymin; + + for(bt= block->buttons.first; bt; bt= bt->next) { + bt->x1 -= ar->winrct.xmin; + bt->x2 -= ar->winrct.xmin; + bt->y1 -= ar->winrct.ymin; + bt->y2 -= ar->winrct.ymin; + } + + block->flag |= UI_BLOCK_LOOP|UI_BLOCK_MOVEMOUSE_QUIT; + + /* adds subwindow */ + ED_region_init(C, ar); + + /* get winmat now that we actually have the subwindow */ + wmSubWindowSet(window, ar->swinid); + + wm_subwindow_getmatrix(window, ar->swinid, block->winmat); + + /* notify change and redraw */ + ED_region_tag_redraw(ar); + + return handle; +} + +void ui_popup_block_free(bContext *C, uiPopupBlockHandle *handle) +{ + /* XXX ton added, chrash on load file with popup open... need investigate */ + if(CTX_wm_screen(C)) + ui_remove_temporary_region(C, CTX_wm_screen(C), handle->region); + MEM_freeN(handle); +} + +/***************************** Menu Button ***************************/ + +uiBlock *ui_block_func_MENU(bContext *C, uiPopupBlockHandle *handle, void *arg_but) +{ + uiBut *but= arg_but; + uiBlock *block; + uiBut *bt; + MenuData *md; + ListBase lb; + float aspect; + int width, height, boxh, columns, rows, startx, starty, x1, y1, xmax, a; + + /* create the block */ + block= uiBeginBlock(C, handle->region, "menu", UI_EMBOSSP); + block->flag= UI_BLOCK_LOOP|UI_BLOCK_REDRAW|UI_BLOCK_NUMSELECT; + + /* compute menu data */ + md= decompose_menu_string(but->str); + + /* columns and row calculation */ + columns= (md->nitems+20)/20; + if(columns<1) + columns= 1; + if(columns>8) + columns= (md->nitems+25)/25; + + rows= md->nitems/columns; + if(rows<1) + rows= 1; + while(rows*columns<md->nitems) + rows++; + + /* prevent scaling up of pupmenu */ + aspect= but->aspect; + if(aspect < 1.0f) + aspect = 1.0f; + + /* size and location */ + if(md->title) + width= 1.5*aspect*strlen(md->title)+UI_GetStringWidth(md->title); + else + width= 0; + + for(a=0; a<md->nitems; a++) { + xmax= aspect*UI_GetStringWidth(md->items[a].str); + if(md->items[a].icon) + xmax += 20*aspect; + if(xmax>width) + width= xmax; + } + + width+= 10; + if(width < (but->x2 - but->x1)) + width = (but->x2 - but->x1); + if(width<50) + width=50; + + boxh= MENU_BUTTON_HEIGHT; + + height= rows*boxh; + if(md->title) + height+= boxh; + + /* here we go! */ + startx= but->x1; + starty= but->y1; + + if(md->title) { + uiBut *bt; + + if (md->titleicon) { + bt= uiDefIconTextBut(block, LABEL, 0, md->titleicon, md->title, startx, (short)(starty+rows*boxh), (short)width, (short)boxh, NULL, 0.0, 0.0, 0, 0, ""); + } else { + bt= uiDefBut(block, LABEL, 0, md->title, startx, (short)(starty+rows*boxh), (short)width, (short)boxh, NULL, 0.0, 0.0, 0, 0, ""); + bt->flag= UI_TEXT_LEFT; + } + } + + for(a=0; a<md->nitems; a++) { + + x1= startx + width*((int)(md->nitems-a-1)/rows); + y1= starty - boxh*(rows - ((md->nitems - a - 1)%rows)) + (rows*boxh); + + if (strcmp(md->items[md->nitems-a-1].str, "%l")==0) { + bt= uiDefBut(block, SEPR, B_NOP, "", x1, y1,(short)(width-(rows>1)), (short)(boxh-1), NULL, 0.0, 0.0, 0, 0, ""); + } + else if(md->items[md->nitems-a-1].icon) { + bt= uiDefIconTextButF(block, BUTM|FLO, B_NOP, md->items[md->nitems-a-1].icon ,md->items[md->nitems-a-1].str, x1, y1,(short)(width-(rows>1)), (short)(boxh-1), &handle->retvalue, (float) md->items[md->nitems-a-1].retval, 0.0, 0, 0, ""); + } + else { + bt= uiDefButF(block, BUTM|FLO, B_NOP, md->items[md->nitems-a-1].str, x1, y1,(short)(width-(rows>1)), (short)(boxh-1), &handle->retvalue, (float) md->items[md->nitems-a-1].retval, 0.0, 0, 0, ""); + } + } + + menudata_free(md); + + /* the code up here has flipped locations, because of change of preferred order */ + /* thats why we have to switch list order too, to make arrowkeys work */ + + lb.first= lb.last= NULL; + bt= block->buttons.first; + while(bt) { + uiBut *next= bt->next; + BLI_remlink(&block->buttons, bt); + BLI_addhead(&lb, bt); + bt= next; + } + block->buttons= lb; + + block->direction= UI_TOP; + uiEndBlock(C, block); + + return block; +} + +uiBlock *ui_block_func_ICONROW(bContext *C, uiPopupBlockHandle *handle, void *arg_but) +{ + uiBut *but= arg_but; + uiBlock *block; + int a; + + block= uiBeginBlock(C, handle->region, "menu", UI_EMBOSSP); + block->flag= UI_BLOCK_LOOP|UI_BLOCK_REDRAW|UI_BLOCK_NUMSELECT; + + for(a=(int)but->hardmin; a<=(int)but->hardmax; a++) { + uiDefIconButF(block, BUTM|FLO, B_NOP, but->icon+(a-but->hardmin), 0, (short)(18*a), (short)(but->x2-but->x1-4), 18, &handle->retvalue, (float)a, 0.0, 0, 0, ""); + } + + block->direction= UI_TOP; + + uiEndBlock(C, block); + + return block; +} + +uiBlock *ui_block_func_ICONTEXTROW(bContext *C, uiPopupBlockHandle *handle, void *arg_but) +{ + uiBut *but= arg_but; + uiBlock *block; + MenuData *md; + int width, xmax, ypos, a; + + block= uiBeginBlock(C, handle->region, "menu", UI_EMBOSSP); + block->flag= UI_BLOCK_LOOP|UI_BLOCK_REDRAW|UI_BLOCK_NUMSELECT; + + md= decompose_menu_string(but->str); + + /* size and location */ + /* expand menu width to fit labels */ + if(md->title) + width= 2*strlen(md->title)+UI_GetStringWidth(md->title); + else + width= 0; + + for(a=0; a<md->nitems; a++) { + xmax= UI_GetStringWidth(md->items[a].str); + if(xmax>width) width= xmax; + } + + width+= 30; + if (width<50) width=50; + + ypos = 1; + + /* loop through the menu options and draw them out with icons & text labels */ + for(a=0; a<md->nitems; a++) { + + /* add a space if there's a separator (%l) */ + if (strcmp(md->items[a].str, "%l")==0) { + ypos +=3; + } + else { + uiDefIconTextButF(block, BUTM|FLO, B_NOP, (short)((but->icon)+(md->items[a].retval-but->hardmin)), md->items[a].str, 0, ypos,(short)width, 19, &handle->retvalue, (float) md->items[a].retval, 0.0, 0, 0, ""); + ypos += 20; + } + } + + if(md->title) { + uiBut *bt; + + bt= uiDefBut(block, LABEL, 0, md->title, 0, ypos, (short)width, 19, NULL, 0.0, 0.0, 0, 0, ""); + bt->flag= UI_TEXT_LEFT; + } + + menudata_free(md); + + block->direction= UI_TOP; + + uiBoundsBlock(block, 3); + uiEndBlock(C, block); + + return block; +} + +static void ui_warp_pointer(short x, short y) +{ + /* XXX 2.50 which function to use for this? */ +#if 0 + /* OSX has very poor mousewarp support, it sends events; + this causes a menu being pressed immediately ... */ + #ifndef __APPLE__ + warp_pointer(x, y); + #endif +#endif +} + +/********************* Color Button ****************/ + +/* picker sizes S hsize, F full size, D spacer, B button/pallette height */ +#define SPICK 110.0 +#define FPICK 180.0 +#define DPICK 6.0 +#define BPICK 24.0 + +#define UI_PALETTE_TOT 16 +/* note; in tot+1 the old color is stored */ +static float palette[UI_PALETTE_TOT+1][3]= { +{0.93, 0.83, 0.81}, {0.88, 0.89, 0.73}, {0.69, 0.81, 0.57}, {0.51, 0.76, 0.64}, +{0.37, 0.56, 0.61}, {0.33, 0.29, 0.55}, {0.46, 0.21, 0.51}, {0.40, 0.12, 0.18}, +{1.0, 1.0, 1.0}, {0.85, 0.85, 0.85}, {0.7, 0.7, 0.7}, {0.56, 0.56, 0.56}, +{0.42, 0.42, 0.42}, {0.28, 0.28, 0.28}, {0.14, 0.14, 0.14}, {0.0, 0.0, 0.0} +}; + +/* for picker, while editing hsv */ +void ui_set_but_hsv(uiBut *but) +{ + float col[3]; + + hsv_to_rgb(but->hsv[0], but->hsv[1], but->hsv[2], col, col+1, col+2); + ui_set_but_vectorf(but, col); +} + +static void update_picker_hex(uiBlock *block, float *rgb) +{ + uiBut *bt; + char col[16]; + + sprintf(col, "%02X%02X%02X", (unsigned int)(rgb[0]*255.0), (unsigned int)(rgb[1]*255.0), (unsigned int)(rgb[2]*255.0)); + + // this updates button strings, is hackish... but button pointers are on stack of caller function + + for(bt= block->buttons.first; bt; bt= bt->next) { + if(strcmp(bt->str, "Hex: ")==0) { + strcpy(bt->poin, col); + ui_check_but(bt); + break; + } + } +} + +void ui_update_block_buts_hsv(uiBlock *block, float *hsv) +{ + uiBut *bt; + float r, g, b; + float rgb[3]; + + // this updates button strings, is hackish... but button pointers are on stack of caller function + hsv_to_rgb(hsv[0], hsv[1], hsv[2], &r, &g, &b); + + rgb[0] = r; rgb[1] = g; rgb[2] = b; + update_picker_hex(block, rgb); + + for(bt= block->buttons.first; bt; bt= bt->next) { + if(bt->type==HSVCUBE) { + VECCOPY(bt->hsv, hsv); + ui_set_but_hsv(bt); + } + else if(bt->str[1]==' ') { + if(bt->str[0]=='R') { + ui_set_but_val(bt, r); + } + else if(bt->str[0]=='G') { + ui_set_but_val(bt, g); + } + else if(bt->str[0]=='B') { + ui_set_but_val(bt, b); + } + else if(bt->str[0]=='H') { + ui_set_but_val(bt, hsv[0]); + } + else if(bt->str[0]=='S') { + ui_set_but_val(bt, hsv[1]); + } + else if(bt->str[0]=='V') { + ui_set_but_val(bt, hsv[2]); + } + } + } +} + +static void ui_update_block_buts_hex(uiBlock *block, char *hexcol) +{ + uiBut *bt; + float r=0, g=0, b=0; + float h, s, v; + + + // this updates button strings, is hackish... but button pointers are on stack of caller function + hex_to_rgb(hexcol, &r, &g, &b); + rgb_to_hsv(r, g, b, &h, &s, &v); + + for(bt= block->buttons.first; bt; bt= bt->next) { + if(bt->type==HSVCUBE) { + bt->hsv[0] = h; + bt->hsv[1] = s; + bt->hsv[2] = v; + ui_set_but_hsv(bt); + } + else if(bt->str[1]==' ') { + if(bt->str[0]=='R') { + ui_set_but_val(bt, r); + } + else if(bt->str[0]=='G') { + ui_set_but_val(bt, g); + } + else if(bt->str[0]=='B') { + ui_set_but_val(bt, b); + } + else if(bt->str[0]=='H') { + ui_set_but_val(bt, h); + } + else if(bt->str[0]=='S') { + ui_set_but_val(bt, s); + } + else if(bt->str[0]=='V') { + ui_set_but_val(bt, v); + } + } + } +} + +/* bt1 is palette but, col1 is original color */ +/* callback to copy from/to palette */ +static void do_palette_cb(bContext *C, void *bt1, void *col1) +{ + wmWindow *win= CTX_wm_window(C); + uiBut *but1= (uiBut *)bt1; + float *col= (float *)col1; + float *fp, hsv[3]; + + fp= (float *)but1->poin; + + if(win->eventstate->ctrl) { + VECCOPY(fp, col); + } + else { + VECCOPY(col, fp); + } + + rgb_to_hsv(col[0], col[1], col[2], hsv, hsv+1, hsv+2); + ui_update_block_buts_hsv(but1->block, hsv); + update_picker_hex(but1->block, col); +} + +/* bt1 is num but, hsv1 is pointer to original color in hsv space*/ +/* callback to handle changes in num-buts in picker */ +static void do_palette1_cb(bContext *C, void *bt1, void *hsv1) +{ + uiBut *but1= (uiBut *)bt1; + float *hsv= (float *)hsv1; + float *fp= NULL; + + if(but1->str[1]==' ') { + if(but1->str[0]=='R') fp= (float *)but1->poin; + else if(but1->str[0]=='G') fp= ((float *)but1->poin)-1; + else if(but1->str[0]=='B') fp= ((float *)but1->poin)-2; + } + if(fp) { + rgb_to_hsv(fp[0], fp[1], fp[2], hsv, hsv+1, hsv+2); + } + ui_update_block_buts_hsv(but1->block, hsv); +} + +/* bt1 is num but, col1 is pointer to original color */ +/* callback to handle changes in num-buts in picker */ +static void do_palette2_cb(bContext *C, void *bt1, void *col1) +{ + uiBut *but1= (uiBut *)bt1; + float *rgb= (float *)col1; + float *fp= NULL; + + if(but1->str[1]==' ') { + if(but1->str[0]=='H') fp= (float *)but1->poin; + else if(but1->str[0]=='S') fp= ((float *)but1->poin)-1; + else if(but1->str[0]=='V') fp= ((float *)but1->poin)-2; + } + if(fp) { + hsv_to_rgb(fp[0], fp[1], fp[2], rgb, rgb+1, rgb+2); + } + ui_update_block_buts_hsv(but1->block, fp); +} + +static void do_palette_hex_cb(bContext *C, void *bt1, void *hexcl) +{ + uiBut *but1= (uiBut *)bt1; + char *hexcol= (char *)hexcl; + + ui_update_block_buts_hex(but1->block, hexcol); +} + +/* used for both 3d view and image window */ +static void do_palette_sample_cb(bContext *C, void *bt1, void *col1) /* frontbuf */ +{ + /* XXX 2.50 this should become an operator? */ +#if 0 + uiBut *but1= (uiBut *)bt1; + uiBut *but; + float tempcol[4]; + int x=0, y=0; + short mval[2]; + float hsv[3]; + short capturing; + int oldcursor; + Window *win; + unsigned short dev; + + oldcursor=get_cursor(); + win=winlay_get_active_window(); + + while (get_mbut() & L_MOUSE) UI_wait_for_statechange(); + + SetBlenderCursor(BC_EYEDROPPER_CURSOR); + + /* loop and wait for a mouse click */ + capturing = TRUE; + while(capturing) { + char ascii; + short val; + + dev = extern_qread_ext(&val, &ascii); + + if(dev==INPUTCHANGE) break; + if(get_mbut() & R_MOUSE) break; + else if(get_mbut() & L_MOUSE) { + uiGetMouse(mywinget(), mval); + x= mval[0]; y= mval[1]; + + capturing = FALSE; + break; + } + else if(dev==ESCKEY) break; + } + window_set_cursor(win, oldcursor); + + if(capturing) return; + + if(x<0 || y<0) return; + + /* if we've got a glick, use OpenGL to sample the color under the mouse pointer */ + glReadBuffer(GL_FRONT); + glReadPixels(x, y, 1, 1, GL_RGBA, GL_FLOAT, tempcol); + glReadBuffer(GL_BACK); + + /* and send that color back to the picker */ + rgb_to_hsv(tempcol[0], tempcol[1], tempcol[2], hsv, hsv+1, hsv+2); + ui_update_block_buts_hsv(but1->block, hsv); + update_picker_hex(but1->block, tempcol); + + for (but= but1->block->buttons.first; but; but= but->next) { + ui_check_but(but); + ui_draw_but(but); + } + + but= but1->block->buttons.first; + ui_block_flush_back(but->block); +#endif +} + +/* color picker, Gimp version. mode: 'f' = floating panel, 'p' = popup */ +/* col = read/write to, hsv/old/hexcol = memory for temporal use */ +void uiBlockPickerButtons(uiBlock *block, float *col, float *hsv, float *old, char *hexcol, char mode, short retval) +{ + uiBut *bt; + float h, offs; + int a; + + VECCOPY(old, col); // old color stored there, for palette_cb to work + + // the cube intersection + bt= uiDefButF(block, HSVCUBE, retval, "", 0,DPICK+BPICK,FPICK,FPICK, col, 0.0, 0.0, 2, 0, ""); + + bt= uiDefButF(block, HSVCUBE, retval, "", 0,0,FPICK,BPICK, col, 0.0, 0.0, 3, 0, ""); + + // palette + + bt=uiDefButF(block, COL, retval, "", FPICK+DPICK, 0, BPICK,BPICK, old, 0.0, 0.0, -1, 0, "Old color, click to restore"); + uiButSetFunc(bt, do_palette_cb, bt, col); + uiDefButF(block, COL, retval, "", FPICK+DPICK, BPICK+DPICK, BPICK,60-BPICK-DPICK, col, 0.0, 0.0, -1, 0, "Active color"); + + h= (DPICK+BPICK+FPICK-64)/(UI_PALETTE_TOT/2.0); + uiBlockBeginAlign(block); + for(a= -1+UI_PALETTE_TOT/2; a>=0; a--) { + bt= uiDefButF(block, COL, retval, "", FPICK+DPICK, 65.0+(float)a*h, BPICK/2, h, palette[a+UI_PALETTE_TOT/2], 0.0, 0.0, -1, 0, "Click to choose, hold CTRL to store in palette"); + uiButSetFunc(bt, do_palette_cb, bt, col); + bt= uiDefButF(block, COL, retval, "", FPICK+DPICK+BPICK/2, 65.0+(float)a*h, BPICK/2, h, palette[a], 0.0, 0.0, -1, 0, "Click to choose, hold CTRL to store in palette"); + uiButSetFunc(bt, do_palette_cb, bt, col); + } + uiBlockEndAlign(block); + + // buttons + rgb_to_hsv(col[0], col[1], col[2], hsv, hsv+1, hsv+2); + sprintf(hexcol, "%02X%02X%02X", (unsigned int)(col[0]*255.0), (unsigned int)(col[1]*255.0), (unsigned int)(col[2]*255.0)); + + offs= FPICK+2*DPICK+BPICK; + + /* note; made this a TOG now, with NULL pointer. Is because BUT now gets handled with a afterfunc */ + bt= uiDefIconTextBut(block, TOG, UI_RETURN_OK, ICON_EYEDROPPER, "Sample", offs+55, 170, 85, 20, NULL, 0, 0, 0, 0, "Sample the color underneath the following mouse click (ESC or RMB to cancel)"); + uiButSetFunc(bt, do_palette_sample_cb, bt, col); + uiButSetFlag(bt, UI_TEXT_LEFT); + + bt= uiDefBut(block, TEX, retval, "Hex: ", offs, 140, 140, 20, hexcol, 0, 8, 0, 0, "Hex triplet for color (#RRGGBB)"); + uiButSetFunc(bt, do_palette_hex_cb, bt, hexcol); + + uiBlockBeginAlign(block); + bt= uiDefButF(block, NUMSLI, retval, "R ", offs, 110, 140,20, col, 0.0, 1.0, 10, 3, ""); + uiButSetFunc(bt, do_palette1_cb, bt, hsv); + bt= uiDefButF(block, NUMSLI, retval, "G ", offs, 90, 140,20, col+1, 0.0, 1.0, 10, 3, ""); + uiButSetFunc(bt, do_palette1_cb, bt, hsv); + bt= uiDefButF(block, NUMSLI, retval, "B ", offs, 70, 140,20, col+2, 0.0, 1.0, 10, 3, ""); + uiButSetFunc(bt, do_palette1_cb, bt, hsv); + + uiBlockBeginAlign(block); + bt= uiDefButF(block, NUMSLI, retval, "H ", offs, 40, 140,20, hsv, 0.0, 1.0, 10, 3, ""); + uiButSetFunc(bt, do_palette2_cb, bt, col); + bt= uiDefButF(block, NUMSLI, retval, "S ", offs, 20, 140,20, hsv+1, 0.0, 1.0, 10, 3, ""); + uiButSetFunc(bt, do_palette2_cb, bt, col); + bt= uiDefButF(block, NUMSLI, retval, "V ", offs, 0, 140,20, hsv+2, 0.0, 1.0, 10, 3, ""); + uiButSetFunc(bt, do_palette2_cb, bt, col); + uiBlockEndAlign(block); +} + +uiBlock *ui_block_func_COL(bContext *C, uiPopupBlockHandle *handle, void *arg_but) +{ + uiBut *but= arg_but; + uiBlock *block; + static float hsvcol[3], oldcol[3]; + static char hexcol[128]; + + block= uiBeginBlock(C, handle->region, "colorpicker", UI_EMBOSS); + block->flag= UI_BLOCK_LOOP|UI_BLOCK_REDRAW|UI_BLOCK_KEEP_OPEN; + + VECCOPY(handle->retvec, but->editvec); + uiBlockPickerButtons(block, handle->retvec, hsvcol, oldcol, hexcol, 'p', 0); + + /* and lets go */ + block->direction= UI_TOP; + uiBoundsBlock(block, 3); + + return block; +} + +/* ******************** Color band *************** */ + +static int vergcband(const void *a1, const void *a2) +{ + const CBData *x1=a1, *x2=a2; + + if( x1->pos > x2->pos ) return 1; + else if( x1->pos < x2->pos) return -1; + return 0; +} + +static void colorband_pos_cb(bContext *C, void *coba_v, void *unused_v) +{ + ColorBand *coba= coba_v; + int a; + + if(coba->tot<2) return; + + for(a=0; a<coba->tot; a++) coba->data[a].cur= a; + qsort(coba->data, coba->tot, sizeof(CBData), vergcband); + for(a=0; a<coba->tot; a++) { + if(coba->data[a].cur==coba->cur) { + /* if(coba->cur!=a) addqueue(curarea->win, REDRAW, 0); */ /* button cur */ + coba->cur= a; + break; + } + } +} + +static void colorband_add_cb(bContext *C, void *coba_v, void *unused_v) +{ + ColorBand *coba= coba_v; + + if(coba->tot < MAXCOLORBAND-1) coba->tot++; + coba->cur= coba->tot-1; + + colorband_pos_cb(C, coba, NULL); +// BIF_undo_push("Add colorband"); + +} + +static void colorband_del_cb(bContext *C, void *coba_v, void *unused_v) +{ + ColorBand *coba= coba_v; + int a; + + if(coba->tot<2) return; + + for(a=coba->cur; a<coba->tot; a++) { + coba->data[a]= coba->data[a+1]; + } + if(coba->cur) coba->cur--; + coba->tot--; + +// BIF_undo_push("Delete colorband"); +// BIF_preview_changed(ID_TE); +} + +void uiBlockColorbandButtons(uiBlock *block, ColorBand *coba, rctf *butr, int event) +{ + CBData *cbd; + uiBut *bt; + float unit= (butr->xmax-butr->xmin)/14.0f; + float xs= butr->xmin; + + cbd= coba->data + coba->cur; + + uiBlockBeginAlign(block); + uiDefButF(block, COL, event, "", xs,butr->ymin+20.0f,2.0f*unit,20, &(cbd->r), 0, 0, 0, 0, ""); + uiDefButF(block, NUM, event, "A:", xs+2.0f*unit,butr->ymin+20.0f,4.0f*unit,20, &(cbd->a), 0.0f, 1.0f, 10, 2, ""); + bt= uiDefBut(block, BUT, event, "Add", xs+6.0f*unit,butr->ymin+20.0f,2.0f*unit,20, NULL, 0, 0, 0, 0, "Adds a new color position to the colorband"); + uiButSetFunc(bt, colorband_add_cb, coba, NULL); + bt= uiDefBut(block, BUT, event, "Del", xs+8.0f*unit, butr->ymin+20.0f, 2.0f*unit, 20, NULL, 0, 0, 0, 0, "Deletes the active position"); + uiButSetFunc(bt, colorband_del_cb, coba, NULL); + + uiDefButS(block, MENU, event, "Interpolation %t|Ease %x1|Cardinal %x3|Linear %x0|B-Spline %x2|Constant %x4", + xs + 10.0f*unit, butr->ymin+20.0f, unit*4.0f, 20, &coba->ipotype, 0.0, 0.0, 0, 0, "Sets interpolation type"); + + uiDefBut(block, BUT_COLORBAND, event, "", xs, butr->ymin, butr->xmax-butr->xmin, 20.0f, coba, 0, 0, 0, 0, ""); + uiBlockEndAlign(block); + +} + + +/* ******************** PUPmenu ****************** */ + +static int pupmenu_set= 0; + +void uiPupMenuSetActive(int val) +{ + pupmenu_set= val; +} + +/* value== -1 read, otherwise set */ +static int pupmenu_memory(char *str, int value) +{ + static char mem[256], first=1; + int val=0, nr=0; + + if(first) { + memset(mem, 0, 256); + first= 0; + } + while(str[nr]) { + val+= str[nr]; + nr++; + } + + if(value >= 0) mem[ val & 255 ]= value; + else return mem[ val & 255 ]; + + return 0; +} + +#define PUP_LABELH 6 + +typedef struct uiPupMenuInfo { + char *instr; + int mx, my; + int startx, starty; + int maxrow; +} uiPupMenuInfo; + +uiBlock *ui_block_func_PUPMENU(bContext *C, uiPopupBlockHandle *handle, void *arg_info) +{ + uiBlock *block; + uiPupMenuInfo *info; + int columns, rows, mousemove[2]= {0, 0}, mousewarp= 0; + int width, height, xmax, ymax, maxrow; + int a, startx, starty, endx, endy, x1, y1; + int lastselected; + MenuData *md; + + info= arg_info; + maxrow= info->maxrow; + height= 0; + + /* block stuff first, need to know the font */ + block= uiBeginBlock(C, handle->region, "menu", UI_EMBOSSP); + uiBlockSetFlag(block, UI_BLOCK_LOOP|UI_BLOCK_REDRAW|UI_BLOCK_RET_1|UI_BLOCK_NUMSELECT); + block->direction= UI_DOWN; + + md= decompose_menu_string(info->instr); + + rows= md->nitems; + columns= 1; + + /* size and location, title slightly bigger for bold */ + if(md->title) { + width= 2*strlen(md->title)+UI_GetStringWidth(md->title); + width /= columns; + } + else width= 0; + + for(a=0; a<md->nitems; a++) { + xmax= UI_GetStringWidth(md->items[a].str); + if(xmax>width) width= xmax; + + if(strcmp(md->items[a].str, "%l")==0) height+= PUP_LABELH; + else height+= MENU_BUTTON_HEIGHT; + } + + width+= 10; + if (width<50) width=50; + + wm_window_get_size(CTX_wm_window(C), &xmax, &ymax); + + /* set first item */ + lastselected= 0; + if(pupmenu_set) { + lastselected= pupmenu_set-1; + pupmenu_set= 0; + } + else if(md->nitems>1) { + lastselected= pupmenu_memory(info->instr, -1); + } + + startx= info->mx-(0.8*(width)); + starty= info->my-height+MENU_BUTTON_HEIGHT/2; + if(lastselected>=0 && lastselected<md->nitems) { + for(a=0; a<md->nitems; a++) { + if(a==lastselected) break; + if( strcmp(md->items[a].str, "%l")==0) starty+= PUP_LABELH; + else starty+=MENU_BUTTON_HEIGHT; + } + + //starty= info->my-height+MENU_BUTTON_HEIGHT/2+lastselected*MENU_BUTTON_HEIGHT; + } + + if(startx<10) { + startx= 10; + } + if(starty<10) { + mousemove[1]= 10-starty; + starty= 10; + } + + endx= startx+width*columns; + endy= starty+height; + + if(endx>xmax) { + endx= xmax-10; + startx= endx-width*columns; + } + if(endy>ymax-20) { + mousemove[1]= ymax-endy-20; + endy= ymax-20; + starty= endy-height; + } + + if(mousemove[0] || mousemove[1]) { + ui_warp_pointer(info->mx+mousemove[0], info->my+mousemove[1]); + mousemove[0]= info->mx; + mousemove[1]= info->my; + mousewarp= 1; + } + + /* here we go! */ + if(md->title) { + uiBut *bt; + char titlestr[256]; + + if(md->titleicon) { + width+= 20; + sprintf(titlestr, " %s", md->title); + uiDefIconTextBut(block, LABEL, 0, md->titleicon, titlestr, startx, (short)(starty+height), width, MENU_BUTTON_HEIGHT, NULL, 0.0, 0.0, 0, 0, ""); + } + else { + bt= uiDefBut(block, LABEL, 0, md->title, startx, (short)(starty+height), columns*width, MENU_BUTTON_HEIGHT, NULL, 0.0, 0.0, 0, 0, ""); + bt->flag= UI_TEXT_LEFT; + } + + //uiDefBut(block, SEPR, 0, "", startx, (short)(starty+height)-MENU_SEPR_HEIGHT, width, MENU_SEPR_HEIGHT, NULL, 0.0, 0.0, 0, 0, ""); + } + + x1= startx + width*((int)a/rows); + y1= starty + height - MENU_BUTTON_HEIGHT; // - MENU_SEPR_HEIGHT; + + for(a=0; a<md->nitems; a++) { + char *name= md->items[a].str; + int icon = md->items[a].icon; + + if(strcmp(name, "%l")==0) { + uiDefBut(block, SEPR, B_NOP, "", x1, y1, width, PUP_LABELH, NULL, 0, 0.0, 0, 0, ""); + y1 -= PUP_LABELH; + } + else if (icon) { + uiDefIconButF(block, BUTM, B_NOP, icon, x1, y1, width+16, MENU_BUTTON_HEIGHT-1, &handle->retvalue, (float) md->items[a].retval, 0.0, 0, 0, ""); + y1 -= MENU_BUTTON_HEIGHT; + } + else { + uiDefButF(block, BUTM, B_NOP, name, x1, y1, width, MENU_BUTTON_HEIGHT-1, &handle->retvalue, (float) md->items[a].retval, 0.0, 0, 0, ""); + y1 -= MENU_BUTTON_HEIGHT; + } + } + + uiBoundsBlock(block, 1); + uiEndBlock(C, block); + + menudata_free(md); + + /* XXX 2.5 need to store last selected */ +#if 0 + /* calculate last selected */ + if(event & ui_return_ok) { + lastselected= 0; + for(a=0; a<md->nitems; a++) { + if(val==md->items[a].retval) lastselected= a; + } + + pupmenu_memory(info->instr, lastselected); + } +#endif + + /* XXX 2.5 need to warp back */ +#if 0 + if(mousemove[1] && (event & ui_return_out)==0) + ui_warp_pointer(mousemove[0], mousemove[1]); + return val; +#endif + + return block; +} + +uiBlock *ui_block_func_PUPMENUCOL(bContext *C, uiPopupBlockHandle *handle, void *arg_info) +{ + uiBlock *block; + uiPupMenuInfo *info; + int columns, rows, mousemove[2]= {0, 0}, mousewarp; + int width, height, xmax, ymax, maxrow; + int a, startx, starty, endx, endy, x1, y1; + float fvalue; + MenuData *md; + + info= arg_info; + maxrow= info->maxrow; + height= 0; + + /* block stuff first, need to know the font */ + block= uiBeginBlock(C, handle->region, "menu", UI_EMBOSSP); + uiBlockSetFlag(block, UI_BLOCK_LOOP|UI_BLOCK_REDRAW|UI_BLOCK_RET_1|UI_BLOCK_NUMSELECT); + block->direction= UI_DOWN; + + md= decompose_menu_string(info->instr); + + /* columns and row calculation */ + columns= (md->nitems+maxrow)/maxrow; + if (columns<1) columns= 1; + + if(columns > 8) { + maxrow += 5; + columns= (md->nitems+maxrow)/maxrow; + } + + rows= (int) md->nitems/columns; + if (rows<1) rows= 1; + + while (rows*columns<(md->nitems+columns) ) rows++; + + /* size and location, title slightly bigger for bold */ + if(md->title) { + width= 2*strlen(md->title)+UI_GetStringWidth(md->title); + width /= columns; + } + else width= 0; + + for(a=0; a<md->nitems; a++) { + xmax= UI_GetStringWidth(md->items[a].str); + if(xmax>width) width= xmax; + } + + width+= 10; + if (width<50) width=50; + + height= rows*MENU_BUTTON_HEIGHT; + if (md->title) height+= MENU_BUTTON_HEIGHT; + + wm_window_get_size(CTX_wm_window(C), &xmax, &ymax); + + /* find active item */ + fvalue= handle->retvalue; + for(a=0; a<md->nitems; a++) { + if( md->items[a].retval== (int)fvalue ) break; + } + + /* no active item? */ + if(a==md->nitems) { + if(md->title) a= -1; + else a= 0; + } + + if(a>0) + startx = info->mx-width/2 - ((int)(a)/rows)*width; + else + startx= info->mx-width/2; + starty = info->my-height + MENU_BUTTON_HEIGHT/2 + ((a)%rows)*MENU_BUTTON_HEIGHT; + + if (md->title) starty+= MENU_BUTTON_HEIGHT; + + if(startx<10) { + mousemove[0]= 10-startx; + startx= 10; + } + if(starty<10) { + mousemove[1]= 10-starty; + starty= 10; + } + + endx= startx+width*columns; + endy= starty+height; + + if(endx>xmax) { + mousemove[0]= xmax-endx-10; + endx= xmax-10; + startx= endx-width*columns; + } + if(endy>ymax) { + mousemove[1]= ymax-endy-10; + endy= ymax-10; + starty= endy-height; + } + + if(mousemove[0] || mousemove[1]) { + ui_warp_pointer(info->mx+mousemove[0], info->my+mousemove[1]); + mousemove[0]= info->mx; + mousemove[1]= info->my; + mousewarp= 1; + } + + /* here we go! */ + if(md->title) { + uiBut *bt; + + if(md->titleicon) { + } + else { + bt= uiDefBut(block, LABEL, 0, md->title, startx, (short)(starty+rows*MENU_BUTTON_HEIGHT), columns*width, MENU_BUTTON_HEIGHT, NULL, 0.0, 0.0, 0, 0, ""); + bt->flag= UI_TEXT_LEFT; + } + } + + for(a=0; a<md->nitems; a++) { + char *name= md->items[a].str; + int icon = md->items[a].icon; + + x1= startx + width*((int)a/rows); + y1= starty - MENU_BUTTON_HEIGHT*(a%rows) + (rows-1)*MENU_BUTTON_HEIGHT; + + if(strcmp(name, "%l")==0) { + uiDefBut(block, SEPR, B_NOP, "", x1, y1, width, PUP_LABELH, NULL, 0, 0.0, 0, 0, ""); + y1 -= PUP_LABELH; + } + else if (icon) { + uiDefIconButF(block, BUTM, B_NOP, icon, x1, y1, width+16, MENU_BUTTON_HEIGHT-1, &handle->retvalue, (float) md->items[a].retval, 0.0, 0, 0, ""); + y1 -= MENU_BUTTON_HEIGHT; + } + else { + uiDefButF(block, BUTM, B_NOP, name, x1, y1, width, MENU_BUTTON_HEIGHT-1, &handle->retvalue, (float) md->items[a].retval, 0.0, 0, 0, ""); + y1 -= MENU_BUTTON_HEIGHT; + } + } + + uiBoundsBlock(block, 1); + uiEndBlock(C, block); + + menudata_free(md); + + /* XXX 2.5 need to warp back */ +#if 0 + if((event & UI_RETURN_OUT)==0) + ui_warp_pointer(mousemove[0], mousemove[1]); +#endif + + return block; +} + +/************************** Menu Definitions ***************************/ + +/* prototype */ +static uiBlock *ui_block_func_MENU_ITEM(bContext *C, uiPopupBlockHandle *handle, void *arg_info); + +struct uiPopupMenu { + uiBlock *block; + uiLayout *layout; +}; + +typedef struct uiMenuInfo { + uiPopupMenu *pup; + int mx, my, popup, slideout; + int startx, starty; +} uiMenuInfo; + +/************************ Menu Definitions to uiBlocks ***********************/ + +const char *ui_menu_enumpropname(char *opname, const char *propname, int retval) +{ + wmOperatorType *ot= WM_operatortype_find(opname); + PointerRNA ptr; + PropertyRNA *prop; + + if(!ot || !ot->srna) + return ""; + + RNA_pointer_create(NULL, ot->srna, NULL, &ptr); + prop= RNA_struct_find_property(&ptr, propname); + + if(prop) { + const EnumPropertyItem *item; + int totitem, i; + + RNA_property_enum_items(&ptr, prop, &item, &totitem); + + for (i=0; i<totitem; i++) { + if(item[i].value==retval) + return item[i].name; + } + } + + return ""; +} + +typedef struct MenuItemLevel { + int opcontext; + char *opname; + char *propname; + PointerRNA rnapoin; +} MenuItemLevel; + +static uiBlock *ui_block_func_MENU_ITEM(bContext *C, uiPopupBlockHandle *handle, void *arg_info) +{ + uiBlock *block; + uiMenuInfo *info= arg_info; + uiPopupMenu *pup; + ScrArea *sa; + ARegion *ar; + + pup= info->pup; + block= pup->block; + + /* block stuff first, need to know the font */ + uiBlockSetRegion(block, handle->region); + block->direction= UI_DOWN; + + uiBlockLayoutResolve(C, block, NULL, NULL); + + if(info->popup) { + uiBlockSetFlag(block, UI_BLOCK_LOOP|UI_BLOCK_REDRAW|UI_BLOCK_NUMSELECT|UI_BLOCK_RET_1); + uiBlockSetDirection(block, UI_DOWN); + + /* here we set an offset for the mouse position */ + uiMenuPopupBoundsBlock(block, 1, 0, 1.5*MENU_BUTTON_HEIGHT); + } + else { + /* for a header menu we set the direction automatic */ + if(!info->slideout) { + sa= CTX_wm_area(C); + ar= CTX_wm_region(C); + + if(sa && sa->headertype==HEADERDOWN) { + if(ar && ar->regiontype == RGN_TYPE_HEADER) { + uiBlockSetDirection(block, UI_TOP); + uiBlockFlipOrder(block); + } + } + } + + uiTextBoundsBlock(block, 50); + } + + /* if menu slides out of other menu, override direction */ + if(info->slideout) + uiBlockSetDirection(block, UI_RIGHT); + + uiEndBlock(C, block); + + return block; +} + +uiPopupBlockHandle *ui_popup_menu_create(bContext *C, ARegion *butregion, uiBut *but, uiMenuCreateFunc menu_func, void *arg) +{ + uiStyle *style= U.uistyles.first; + uiPopupBlockHandle *handle; + uiPopupMenu *pup; + uiMenuInfo info; + + pup= MEM_callocN(sizeof(uiPopupMenu), "menu dummy"); + pup->block= uiBeginBlock(C, NULL, "ui_popup_menu_create", UI_EMBOSSP); + pup->layout= uiBlockLayout(pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, style); + uiLayoutSetOperatorContext(pup->layout, WM_OP_INVOKE_REGION_WIN); + + /* create in advance so we can let buttons point to retval already */ + pup->block->handle= MEM_callocN(sizeof(uiPopupBlockHandle), "uiPopupBlockHandle"); + + menu_func(C, pup->layout, arg); + + memset(&info, 0, sizeof(info)); + info.pup= pup; + info.slideout= (but && (but->block->flag & UI_BLOCK_LOOP)); + + handle= ui_popup_block_create(C, butregion, but, NULL, ui_block_func_MENU_ITEM, &info); + + MEM_freeN(pup); + + return handle; +} + +/*************************** Menu Creating API **************************/ + + +/*************************** Popup Menu API **************************/ + +/* only return handler, and set optional title */ +uiPopupMenu *uiPupMenuBegin(bContext *C, const char *title, int icon) +{ + uiStyle *style= U.uistyles.first; + uiPopupMenu *pup= MEM_callocN(sizeof(uiPopupMenu), "menu start"); + uiBut *but; + + pup->block= uiBeginBlock(C, NULL, "uiPupMenuBegin", UI_EMBOSSP); + pup->layout= uiBlockLayout(pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, style); + uiLayoutSetOperatorContext(pup->layout, WM_OP_EXEC_REGION_WIN); + + /* create in advance so we can let buttons point to retval already */ + pup->block->handle= MEM_callocN(sizeof(uiPopupBlockHandle), "uiPopupBlockHandle"); + + /* create title button */ + if(title && title[0]) { + char titlestr[256]; + + if(icon) { + sprintf(titlestr, " %s", title); + uiDefIconTextBut(pup->block, LABEL, 0, icon, titlestr, 0, 0, 200, MENU_BUTTON_HEIGHT, NULL, 0.0, 0.0, 0, 0, ""); + } + else { + but= uiDefBut(pup->block, LABEL, 0, (char*)title, 0, 0, 200, MENU_BUTTON_HEIGHT, NULL, 0.0, 0.0, 0, 0, ""); + but->flag= UI_TEXT_LEFT; + } + + //uiDefBut(block, SEPR, 0, "", startx, (short)(starty+height)-MENU_SEPR_HEIGHT, width, MENU_SEPR_HEIGHT, NULL, 0.0, 0.0, 0, 0, ""); + } + + return pup; +} + +/* set the whole structure to work */ +void uiPupMenuEnd(bContext *C, uiPopupMenu *pup) +{ + wmWindow *window= CTX_wm_window(C); + uiMenuInfo info; + uiPopupBlockHandle *menu; + + memset(&info, 0, sizeof(info)); + info.popup= 1; + info.mx= window->eventstate->x; + info.my= window->eventstate->y; + info.pup= pup; + + menu= ui_popup_block_create(C, NULL, NULL, NULL, ui_block_func_MENU_ITEM, &info); + menu->popup= 1; + + UI_add_popup_handlers(C, &window->handlers, menu); + WM_event_add_mousemove(C); + + MEM_freeN(pup); +} + +uiLayout *uiPupMenuLayout(uiPopupMenu *pup) +{ + return pup->layout; +} + +/* ************** standard pupmenus *************** */ + +/* this one can called with operatortype name and operators */ +static uiPopupBlockHandle *ui_pup_menu(bContext *C, int maxrow, uiMenuHandleFunc func, void *arg, char *str, ...) +{ + wmWindow *window= CTX_wm_window(C); + uiPupMenuInfo info; + uiPopupBlockHandle *menu; + + memset(&info, 0, sizeof(info)); + info.mx= window->eventstate->x; + info.my= window->eventstate->y; + info.maxrow= maxrow; + info.instr= str; + + menu= ui_popup_block_create(C, NULL, NULL, NULL, ui_block_func_PUPMENU, &info); + menu->popup= 1; + + UI_add_popup_handlers(C, &window->handlers, menu); + WM_event_add_mousemove(C); + + menu->popup_func= func; + menu->popup_arg= arg; + + return menu; +} + + +static void operator_name_cb(bContext *C, void *arg, int retval) +{ + const char *opname= arg; + + if(opname && retval > 0) + WM_operator_name_call(C, opname, WM_OP_EXEC_DEFAULT, NULL); +} + +static void vconfirm_opname(bContext *C, char *opname, char *title, char *itemfmt, va_list ap) +{ + char *s, buf[512]; + + s= buf; + if (title) s+= sprintf(s, "%s%%t|", title); + vsprintf(s, itemfmt, ap); + + ui_pup_menu(C, 0, operator_name_cb, opname, buf); +} + +static void operator_cb(bContext *C, void *arg, int retval) +{ + wmOperator *op= arg; + + if(op && retval > 0) + WM_operator_call(C, op); + else + WM_operator_free(op); +} + +static void confirm_cancel_operator(void *opv) +{ + WM_operator_free(opv); +} + +static void confirm_operator(bContext *C, wmOperator *op, char *title, char *item) +{ + uiPopupBlockHandle *handle; + char *s, buf[512]; + + s= buf; + if (title) s+= sprintf(s, "%s%%t|%s", title, item); + + handle= ui_pup_menu(C, 0, operator_cb, op, buf); + handle->cancel_func= confirm_cancel_operator; +} + + +void uiPupMenuOkee(bContext *C, char *opname, char *str, ...) +{ + va_list ap; + char titlestr[256]; + + sprintf(titlestr, "OK? %%i%d", ICON_QUESTION); + + va_start(ap, str); + vconfirm_opname(C, opname, titlestr, str, ap); + va_end(ap); +} + + +void uiPupMenuSaveOver(bContext *C, wmOperator *op, char *filename) +{ + size_t len= strlen(filename); + + if(len==0) + return; + + if(filename[len-1]=='/' || filename[len-1]=='\\') { + uiPupMenuError(C, "Cannot overwrite a directory"); + WM_operator_free(op); + return; + } + if(BLI_exists(filename)==0) + operator_cb(C, op, 1); + else + confirm_operator(C, op, "Save over", filename); +} + +void uiPupMenuNotice(bContext *C, char *str, ...) +{ + va_list ap; + + va_start(ap, str); + vconfirm_opname(C, NULL, NULL, str, ap); + va_end(ap); +} + +void uiPupMenuError(bContext *C, char *str, ...) +{ + va_list ap; + char nfmt[256]; + char titlestr[256]; + + sprintf(titlestr, "Error %%i%d", ICON_ERROR); + + sprintf(nfmt, "%s", str); + + va_start(ap, str); + vconfirm_opname(C, NULL, titlestr, nfmt, ap); + va_end(ap); +} + +void uiPupMenuReports(bContext *C, ReportList *reports) +{ + Report *report; + DynStr *ds; + char *str; + + if(!reports || !reports->list.first) + return; + if(!CTX_wm_window(C)) + return; + + ds= BLI_dynstr_new(); + + for(report=reports->list.first; report; report=report->next) { + if(report->type >= RPT_ERROR) + BLI_dynstr_appendf(ds, "Error %%i%d%%t|%s", ICON_ERROR, report->message); + else if(report->type >= RPT_WARNING) + BLI_dynstr_appendf(ds, "Warning %%i%d%%t|%s", ICON_ERROR, report->message); + } + + str= BLI_dynstr_get_cstring(ds); + ui_pup_menu(C, 0, NULL, NULL, str); + MEM_freeN(str); + + BLI_dynstr_free(ds); +} + +/*************************** Popup Block API **************************/ + +void uiPupBlockO(bContext *C, uiBlockCreateFunc func, void *arg, char *opname, int opcontext) +{ + wmWindow *window= CTX_wm_window(C); + uiPopupBlockHandle *handle; + + handle= ui_popup_block_create(C, NULL, NULL, func, NULL, arg); + handle->popup= 1; + handle->optype= (opname)? WM_operatortype_find(opname): NULL; + handle->opcontext= opcontext; + + UI_add_popup_handlers(C, &window->handlers, handle); + WM_event_add_mousemove(C); +} + +void uiPupBlock(bContext *C, uiBlockCreateFunc func, void *arg) +{ + uiPupBlockO(C, func, arg, NULL, 0); +} + +void uiPupBlockOperator(bContext *C, uiBlockCreateFunc func, wmOperator *op, int opcontext) +{ + wmWindow *window= CTX_wm_window(C); + uiPopupBlockHandle *handle; + + handle= ui_popup_block_create(C, NULL, NULL, func, NULL, op); + handle->popup= 1; + handle->retvalue= 1; + + handle->popup_arg= op; + handle->popup_func= operator_cb; + handle->cancel_func= confirm_cancel_operator; + handle->opcontext= opcontext; + + UI_add_popup_handlers(C, &window->handlers, handle); + WM_event_add_mousemove(C); +} diff --git a/source/blender/editors/interface/interface_style.c b/source/blender/editors/interface/interface_style.c new file mode 100644 index 00000000000..62a4c01bc6c --- /dev/null +++ b/source/blender/editors/interface/interface_style.c @@ -0,0 +1,267 @@ +/** +* ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2009 Blender Foundation. + * All rights reserved. + * + * Contributor(s): Blender Foundation + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include <limits.h> +#include <math.h> +#include <stdlib.h> +#include <string.h> + +#include "MEM_guardedalloc.h" + +#include "DNA_ID.h" +#include "DNA_screen_types.h" +#include "DNA_userdef_types.h" +#include "DNA_windowmanager_types.h" + +#include "BLI_arithb.h" +#include "BLI_listbase.h" +#include "BLI_rect.h" +#include "BLI_string.h" + +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_utildefines.h" + +#include "BIF_gl.h" +#include "BIF_glutil.h" + +#include "BLF_api.h" + +#include "UI_interface.h" +#include "UI_interface_icons.h" +#include "UI_resources.h" +#include "UI_view2d.h" + +#include "ED_datafiles.h" +#include "ED_util.h" +#include "ED_types.h" + +#include "interface_intern.h" + + +/* style + theme + layout-engine = UI */ + +/* + This is a complete set of layout rules, the 'state' of the Layout + Engine. Multiple styles are possible, defined via C or Python. Styles + get a name, and will typically get activated per region type, like + "Header", or "Listview" or "Toolbar". Properties of Style definitions + are: + + - default collumn properties, internal spacing, aligning, min/max width + - button alignment rules (for groups) + - label placement rules + - internal labeling or external labeling default + - default minimum widths for buttons/labels (in amount of characters) + - font types, styles and relative sizes for Panel titles, labels, etc. + +*/ + + +/* ********************************************** */ + +static uiStyle *ui_style_new(ListBase *styles, const char *name) +{ + uiStyle *style= MEM_callocN(sizeof(uiStyle), "new style"); + + BLI_addtail(styles, style); + BLI_strncpy(style->name, name, MAX_STYLE_NAME); + + style->paneltitle.uifont_id= UIFONT_DEFAULT; + style->paneltitle.points= 13; + style->paneltitle.shadow= 5; + style->paneltitle.shadx= 2; + style->paneltitle.shady= -2; + style->paneltitle.shadowalpha= 0.25f; + style->paneltitle.shadowcolor= 0.0f; + + style->grouplabel.uifont_id= UIFONT_DEFAULT; + style->grouplabel.points= 12; + style->grouplabel.shadow= 3; + style->grouplabel.shadx= 1; + style->grouplabel.shady= -1; + style->grouplabel.shadowalpha= 0.25f; + + style->widgetlabel.uifont_id= UIFONT_DEFAULT; + style->widgetlabel.points= 11; + style->widgetlabel.shadow= 3; + style->widgetlabel.shadx= 1; + style->widgetlabel.shady= -1; + style->widgetlabel.shadowalpha= 0.3f; + style->widgetlabel.shadowcolor= 1.0f; + + style->widget.uifont_id= UIFONT_DEFAULT; + style->widget.points= 11; + style->widget.shadowalpha= 0.25f; + + style->columnspace= 5; + style->templatespace= 5; + style->boxspace= 5; + style->buttonspacex= 5; + style->buttonspacey= 2; + style->panelspace= 8; + style->panelouter= 4; + + return style; +} + +static uiFont *uifont_to_blfont(int id) +{ + uiFont *font= U.uifonts.first; + + for(; font; font= font->next) { + if(font->uifont_id==id) { + return font; + } + } + return U.uifonts.first; +} + +/* *************** draw ************************ */ + +static void ui_font_shadow_draw(uiFontStyle *fs, int x, int y, char *str) +{ + float color[4]; + + glGetFloatv(GL_CURRENT_COLOR, color); + + glColor4f(fs->shadowcolor, fs->shadowcolor, fs->shadowcolor, fs->shadowalpha); + + BLF_blur(fs->shadow); + BLF_position(x+fs->shadx, y+fs->shady, 0.0f); + BLF_draw(str); + BLF_blur(0); + + glColor4fv(color); +} + +void uiStyleFontDraw(uiFontStyle *fs, rcti *rect, char *str) +{ + float height; + int xofs=0, yofs; + + uiStyleFontSet(fs); + + height= BLF_height("2"); /* correct offset is on baseline, the j is below that */ + yofs= floor( 0.5f*(rect->ymax - rect->ymin - height)); + + if(fs->align==UI_STYLE_TEXT_CENTER) + xofs= floor( 0.5f*(rect->xmax - rect->xmin - BLF_width(str))); + else if(fs->align==UI_STYLE_TEXT_RIGHT) + xofs= rect->xmax - rect->xmin - BLF_width(str) - 1; + + /* clip is very strict, so we give it some space */ + BLF_clipping(rect->xmin-1, rect->ymin-4, rect->xmax+1, rect->ymax+4); + BLF_enable(BLF_CLIPPING); + + if(fs->shadow) + ui_font_shadow_draw(fs, rect->xmin+xofs, rect->ymin+yofs, str); + + BLF_position(rect->xmin+xofs, rect->ymin+yofs, 0.0f); + BLF_draw(str); + + BLF_disable(BLF_CLIPPING); +} + +/* ************** helpers ************************ */ + +/* temporarily, does widget font */ +int UI_GetStringWidth(char *str) +{ + uiStyle *style= U.uistyles.first; + + uiStyleFontSet(&style->widget); + return BLF_width(str); +} + +/* temporarily, does widget font */ +void UI_DrawString(float x, float y, char *str) +{ + uiStyle *style= U.uistyles.first; + + uiStyleFontSet(&style->widget); + BLF_position(x, y, 0.0f); + BLF_draw(str); +} + +/* ************** init exit ************************ */ + +/* called on each .B.blend read */ +/* reading without uifont will create one */ +void uiStyleInit(void) +{ + uiFont *font= U.uifonts.first; + uiStyle *style= U.uistyles.first; + + /* recover from uninitialized dpi */ + CLAMP(U.dpi, 72, 240); + + /* default builtin */ + if(font==NULL) { + font= MEM_callocN(sizeof(uiFont), "ui font"); + BLI_addtail(&U.uifonts, font); + + strcpy(font->filename, "default"); + font->uifont_id= UIFONT_DEFAULT; + } + + for(font= U.uifonts.first; font; font= font->next) { + + if(font->uifont_id==UIFONT_DEFAULT) { + font->blf_id= BLF_load_mem("default", (unsigned char*)datatoc_bfont_ttf, datatoc_bfont_ttf_size); + } + else { + font->blf_id= BLF_load(font->filename); + if(font->blf_id == -1) + font->blf_id= BLF_load_mem("default", (unsigned char*)datatoc_bfont_ttf, datatoc_bfont_ttf_size); + } + + if (font->blf_id == -1) + printf("uiStyleInit error, no fonts available\n"); + else { + BLF_set(font->blf_id); + /* ? just for speed to initialize? + * Yes, this build the glyph cache and create + * the texture. + */ + BLF_size(11, U.dpi); + BLF_size(12, U.dpi); + BLF_size(14, U.dpi); + } + } + + if(style==NULL) { + ui_style_new(&U.uistyles, "Default Style"); + } +} + +void uiStyleFontSet(uiFontStyle *fs) +{ + uiFont *font= uifont_to_blfont(fs->uifont_id); + + BLF_set(font->blf_id); + BLF_size(fs->points, U.dpi); +} + diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c new file mode 100644 index 00000000000..020d442bfe6 --- /dev/null +++ b/source/blender/editors/interface/interface_templates.c @@ -0,0 +1,1379 @@ +/** + * $Id$ + * + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contributor(s): Blender Foundation 2009. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include <stdlib.h> +#include <string.h> + +#include "MEM_guardedalloc.h" + +#include "BLI_string.h" + +#include "BKE_context.h" +#include "BKE_library.h" +#include "BKE_utildefines.h" + +#include "ED_screen.h" +#include "ED_previewrender.h" + +#include "RNA_access.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +void ui_template_fix_linking() +{ +} + +/********************** Header Template *************************/ + +void uiTemplateHeader(uiLayout *layout, bContext *C) +{ + uiBlock *block; + + block= uiLayoutFreeBlock(layout); + ED_area_header_standardbuttons(C, block, 0); +} + +/******************* Header ID Template ************************/ + +typedef struct TemplateID { + PointerRNA ptr; + PropertyRNA *prop; + + int flag; + short browse; + + char newop[256]; + char openop[256]; + char unlinkop[256]; + + short idtype; +} TemplateID; + +static void template_id_cb(bContext *C, void *arg_litem, void *arg_event) +{ + TemplateID *template= (TemplateID*)arg_litem; + PointerRNA idptr= RNA_property_pointer_get(&template->ptr, template->prop); + ID *idtest, *id= idptr.data; + ListBase *lb= wich_libbase(CTX_data_main(C), template->idtype); + int nr, event= GET_INT_FROM_POINTER(arg_event); + + if(event == UI_ID_BROWSE && template->browse == 32767) + event= UI_ID_ADD_NEW; + else if(event == UI_ID_BROWSE && template->browse == 32766) + event= UI_ID_OPEN; + + switch(event) { + case UI_ID_BROWSE: { + if(template->browse== -2) { + /* XXX implement or find a replacement + * activate_databrowse((ID *)G.buts->lockpoin, GS(id->name), 0, B_MESHBROWSE, &template->browse, do_global_buttons); */ + return; + } + if(template->browse < 0) + return; + + for(idtest=lb->first, nr=1; idtest; idtest=idtest->next, nr++) { + if(nr==template->browse) { + if(id == idtest) + return; + + id= idtest; + RNA_id_pointer_create(id, &idptr); + RNA_property_pointer_set(&template->ptr, template->prop, idptr); + RNA_property_update(C, &template->ptr, template->prop); + /* XXX */ + + break; + } + } + break; + } + case UI_ID_DELETE: + memset(&idptr, 0, sizeof(idptr)); + RNA_property_pointer_set(&template->ptr, template->prop, idptr); + RNA_property_update(C, &template->ptr, template->prop); + break; + case UI_ID_FAKE_USER: + if(id) { + if(id->flag & LIB_FAKEUSER) id->us++; + else id->us--; + } + else return; + break; + case UI_ID_PIN: + break; + case UI_ID_ADD_NEW: + WM_operator_name_call(C, template->newop, WM_OP_INVOKE_REGION_WIN, NULL); + break; + case UI_ID_OPEN: + WM_operator_name_call(C, template->openop, WM_OP_INVOKE_REGION_WIN, NULL); + break; +#if 0 + case UI_ID_ALONE: + if(!id || id->us < 1) + return; + break; + case UI_ID_LOCAL: + if(!id || id->us < 1) + return; + break; + case UI_ID_AUTO_NAME: + break; +#endif + } +} + +static void template_header_ID(bContext *C, uiBlock *block, TemplateID *template, StructRNA *type) +{ + uiBut *but; + TemplateID *duptemplate; + PointerRNA idptr; + ListBase *lb; + + idptr= RNA_property_pointer_get(&template->ptr, template->prop); + lb= wich_libbase(CTX_data_main(C), template->idtype); + + if(idptr.type) + type= idptr.type; + if(type) + uiDefIconBut(block, LABEL, 0, RNA_struct_ui_icon(type), 0, 0, UI_UNIT_X, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, ""); + + uiBlockBeginAlign(block); + if(template->flag & UI_ID_BROWSE) { + char *extrastr, *str; + + if((template->flag & UI_ID_ADD_NEW) && (template->flag & UI_ID_OPEN)) + extrastr= "OPEN NEW %x 32766 |ADD NEW %x 32767"; + else if(template->flag & UI_ID_ADD_NEW) + extrastr= "ADD NEW %x 32767"; + else if(template->flag & UI_ID_OPEN) + extrastr= "OPEN NEW %x 32766"; + else + extrastr= NULL; + + duptemplate= MEM_dupallocN(template); + IDnames_to_pupstring(&str, NULL, extrastr, lb, idptr.data, &duptemplate->browse); + + but= uiDefButS(block, MENU, 0, str, 0, 0, UI_UNIT_X, UI_UNIT_Y, &duptemplate->browse, 0, 0, 0, 0, "Browse existing choices, or add new"); + uiButSetNFunc(but, template_id_cb, duptemplate, SET_INT_IN_POINTER(UI_ID_BROWSE)); + + MEM_freeN(str); + } + + /* text button with name */ + if(idptr.data) { + char name[64]; + + //text_idbutton(idptr.data, name); + name[0]= '\0'; + but= uiDefButR(block, TEX, 0, name, 0, 0, UI_UNIT_X*6, UI_UNIT_Y, &idptr, "name", -1, 0, 0, -1, -1, NULL); + uiButSetNFunc(but, template_id_cb, MEM_dupallocN(template), SET_INT_IN_POINTER(UI_ID_RENAME)); + + /* delete button */ + if(template->flag & UI_ID_DELETE) { + if(template->unlinkop[0]) { + but= uiDefIconButO(block, BUT, template->unlinkop, WM_OP_EXEC_REGION_WIN, ICON_X, 0, 0, UI_UNIT_X, UI_UNIT_Y, NULL); + } + else { + but= uiDefIconBut(block, BUT, 0, ICON_X, 0, 0, UI_UNIT_X, UI_UNIT_Y, NULL, 0, 0, 0, 0, NULL); + uiButSetNFunc(but, template_id_cb, MEM_dupallocN(template), SET_INT_IN_POINTER(UI_ID_DELETE)); + } + } + } + uiBlockEndAlign(block); +} + +void uiTemplateID(uiLayout *layout, bContext *C, PointerRNA *ptr, char *propname, char *newop, char *openop, char *unlinkop) +{ + TemplateID *template; + uiBlock *block; + PropertyRNA *prop; + StructRNA *type; + + if(!ptr->data) + return; + + prop= RNA_struct_find_property(ptr, propname); + + if(!prop || RNA_property_type(prop) != PROP_POINTER) { + printf("uiTemplateID: pointer property not found: %s\n", propname); + return; + } + + template= MEM_callocN(sizeof(TemplateID), "TemplateID"); + template->ptr= *ptr; + template->prop= prop; + template->flag= UI_ID_BROWSE|UI_ID_RENAME|UI_ID_DELETE; + + if(newop) { + template->flag |= UI_ID_ADD_NEW; + BLI_strncpy(template->newop, newop, sizeof(template->newop)); + } + if(openop) { + template->flag |= UI_ID_OPEN; + BLI_strncpy(template->openop, openop, sizeof(template->openop)); + } + if(unlinkop) + BLI_strncpy(template->unlinkop, unlinkop, sizeof(template->unlinkop)); + + type= RNA_property_pointer_type(ptr, prop); + template->idtype = RNA_type_to_ID_code(type); + + if(template->idtype) { + uiLayoutRow(layout, 1); + block= uiLayoutGetBlock(layout); + template_header_ID(C, block, template, type); + } + + MEM_freeN(template); +} + +/************************ Modifier Template *************************/ + +#define ERROR_LIBDATA_MESSAGE "Can't edit external libdata" + +#include <string.h> + +#include "DNA_object_force.h" +#include "DNA_object_types.h" +#include "DNA_modifier_types.h" +#include "DNA_scene_types.h" + +#include "BKE_depsgraph.h" +#include "BKE_DerivedMesh.h" +#include "BKE_global.h" +#include "BKE_modifier.h" +#include "BKE_object.h" +#include "BKE_particle.h" +#include "BKE_report.h" + +#include "UI_resources.h" +#include "ED_util.h" + +#include "BLI_arithb.h" +#include "BLI_listbase.h" + +#include "ED_object.h" + +static void modifiers_del(bContext *C, void *ob_v, void *md_v) +{ + Scene *scene= CTX_data_scene(C); + Object *ob= ob_v; + ReportList reports; + + BKE_reports_init(&reports, RPT_STORE); + + if(ED_object_modifier_delete(&reports, ob_v, md_v)) { + WM_event_add_notifier(C, NC_OBJECT|ND_MODIFIER, ob); + DAG_object_flush_update(scene, ob, OB_RECALC_DATA); + + ED_undo_push(C, "Delete modifier"); + } + else + uiPupMenuReports(C, &reports); + + BKE_reports_clear(&reports); +} + +static void modifiers_moveUp(bContext *C, void *ob_v, void *md_v) +{ + Scene *scene= CTX_data_scene(C); + Object *ob= ob_v; + ReportList reports; + + BKE_reports_init(&reports, RPT_STORE); + + if(ED_object_modifier_move_up(&reports, ob_v, md_v)) { + WM_event_add_notifier(C, NC_OBJECT|ND_MODIFIER, ob); + DAG_object_flush_update(scene, ob, OB_RECALC_DATA); + + ED_undo_push(C, "Move modifier"); + } + else + uiPupMenuReports(C, &reports); + + BKE_reports_clear(&reports); +} + +static void modifiers_moveDown(bContext *C, void *ob_v, void *md_v) +{ + Scene *scene= CTX_data_scene(C); + Object *ob= ob_v; + ReportList reports; + + BKE_reports_init(&reports, RPT_STORE); + + if(ED_object_modifier_move_down(&reports, ob_v, md_v)) { + WM_event_add_notifier(C, NC_OBJECT|ND_MODIFIER, ob); + DAG_object_flush_update(scene, ob, OB_RECALC_DATA); + + ED_undo_push(C, "Move modifier"); + } + else + uiPupMenuReports(C, &reports); + + BKE_reports_clear(&reports); +} + +static void modifiers_convertParticles(bContext *C, void *obv, void *mdv) +{ + Scene *scene= CTX_data_scene(C); + Object *ob= obv; + ReportList reports; + + BKE_reports_init(&reports, RPT_STORE); + + if(ED_object_modifier_convert(&reports, scene, obv, mdv)) { + WM_event_add_notifier(C, NC_OBJECT|ND_MODIFIER, ob); + DAG_object_flush_update(scene, ob, OB_RECALC_DATA); + + ED_undo_push(C, "Convert particles to mesh object(s)."); + } + else + uiPupMenuReports(C, &reports); + + BKE_reports_clear(&reports); +} + +static void modifiers_applyModifier(bContext *C, void *obv, void *mdv) +{ + Scene *scene= CTX_data_scene(C); + Object *ob= obv; + ReportList reports; + + BKE_reports_init(&reports, RPT_STORE); + + if(ED_object_modifier_apply(&reports, scene, obv, mdv)) { + WM_event_add_notifier(C, NC_OBJECT|ND_MODIFIER, ob); + DAG_object_flush_update(scene, ob, OB_RECALC_DATA); + + ED_undo_push(C, "Apply modifier"); + } + else + uiPupMenuReports(C, &reports); + + BKE_reports_clear(&reports); +} + +static void modifiers_copyModifier(bContext *C, void *ob_v, void *md_v) +{ + Scene *scene= CTX_data_scene(C); + Object *ob= ob_v; + ReportList reports; + + BKE_reports_init(&reports, RPT_STORE); + + if(ED_object_modifier_copy(&reports, ob_v, md_v)) { + WM_event_add_notifier(C, NC_OBJECT|ND_MODIFIER, ob); + DAG_object_flush_update(scene, ob, OB_RECALC_DATA); + ED_undo_push(C, "Copy modifier"); + } + else + uiPupMenuReports(C, &reports); + + BKE_reports_clear(&reports); +} + +static void modifiers_setOnCage(bContext *C, void *ob_v, void *md_v) +{ + Scene *scene= CTX_data_scene(C); + Object *ob = ob_v; + ModifierData *md; + + int i, cageIndex = modifiers_getCageIndex(ob, NULL ); + + for(i = 0, md=ob->modifiers.first; md; ++i, md=md->next) { + if(md == md_v) { + if(i >= cageIndex) + md->mode ^= eModifierMode_OnCage; + break; + } + } + + WM_event_add_notifier(C, NC_OBJECT|ND_MODIFIER, ob); + DAG_object_flush_update(scene, ob, OB_RECALC_DATA); +} + +static void modifiers_convertToReal(bContext *C, void *ob_v, void *md_v) +{ + Scene *scene= CTX_data_scene(C); + Object *ob = ob_v; + ModifierData *md = md_v; + ModifierData *nmd = modifier_new(md->type); + + modifier_copyData(md, nmd); + nmd->mode &= ~eModifierMode_Virtual; + + BLI_addhead(&ob->modifiers, nmd); + + ob->partype = PAROBJECT; + + WM_event_add_notifier(C, NC_OBJECT|ND_MODIFIER, ob); + DAG_object_flush_update(scene, ob, OB_RECALC_DATA); + + ED_undo_push(C, "Modifier convert to real"); +} + +static int modifier_can_delete(ModifierData *md) +{ + // deletion over the deflection panel + // fluid particle modifier can't be deleted here + + if(md->type==eModifierType_Fluidsim) + return 0; + if(md->type==eModifierType_Collision) + return 0; + if(md->type==eModifierType_Surface) + return 0; + if(md->type == eModifierType_ParticleSystem) + if(((ParticleSystemModifierData *)md)->psys->part->type == PART_FLUID) + return 0; + + return 1; +} + +static uiLayout *draw_modifier(uiLayout *layout, Object *ob, ModifierData *md, int index, int cageIndex, int lastCageIndex) +{ + ModifierTypeInfo *mti = modifierType_getInfo(md->type); + PointerRNA ptr; + uiBut *but; + uiBlock *block; + uiLayout *column, *row, *subrow, *result= NULL; + int isVirtual = md->mode&eModifierMode_Virtual; + // XXX short color = md->error?TH_REDALERT:TH_BUT_NEUTRAL; + short width = 295, buttonWidth = width-120-10; + char str[128]; + + RNA_pointer_create(&ob->id, &RNA_Modifier, md, &ptr); + + column= uiLayoutColumn(layout, 1); + uiLayoutSetContextPointer(column, "modifier", &ptr); + + /* rounded header */ + /* XXX uiBlockSetCol(block, color); */ + /* roundbox 4 free variables: corner-rounding, nop, roundbox type, shade */ + + row= uiLayoutRow(uiLayoutBox(column), 0); + block= uiLayoutGetBlock(row); + + subrow= uiLayoutRow(row, 0); + uiLayoutSetAlignment(subrow, UI_LAYOUT_ALIGN_LEFT); + + //uiDefBut(block, ROUNDBOX, 0, "", x-10, y-4, width, 25, NULL, 7.0, 0.0, + // (!isVirtual && (md->mode&eModifierMode_Expanded))?3:15, 20, ""); + /* XXX uiBlockSetCol(block, TH_AUTO); */ + + /* open/close icon */ + if (!isVirtual) { + uiBlockSetEmboss(block, UI_EMBOSSN); + uiDefIconButBitI(block, ICONTOG, eModifierMode_Expanded, 0, ICON_TRIA_RIGHT, 0, 0, UI_UNIT_X, UI_UNIT_Y, &md->mode, 0.0, 0.0, 0.0, 0.0, "Collapse/Expand Modifier"); + } + + uiBlockSetEmboss(block, UI_EMBOSS); + + if (isVirtual) { + sprintf(str, "%s parent deform", md->name); + uiDefBut(block, LABEL, 0, str, 0, 0, 185, UI_UNIT_Y, NULL, 0.0, 0.0, 0.0, 0.0, "Modifier name"); + + but = uiDefBut(block, BUT, 0, "Make Real", 0, 0, 80, 16, NULL, 0.0, 0.0, 0.0, 0.0, "Convert virtual modifier to a real modifier"); + uiButSetFunc(but, modifiers_convertToReal, ob, md); + } else { + uiBlockBeginAlign(block); + uiDefBut(block, TEX, 0, "", 0, 0, buttonWidth-60, UI_UNIT_Y, md->name, 0.0, sizeof(md->name)-1, 0.0, 0.0, "Modifier name"); + + /* Softbody not allowed in this situation, enforce! */ + if (((md->type!=eModifierType_Softbody && md->type!=eModifierType_Collision) || !(ob->pd && ob->pd->deflect)) && (md->type!=eModifierType_Surface)) { + uiDefIconButBitI(block, TOG, eModifierMode_Render, 0, ICON_SCENE, 0, 0, 19, UI_UNIT_Y,&md->mode, 0, 0, 1, 0, "Enable modifier during rendering"); + but= uiDefIconButBitI(block, TOG, eModifierMode_Realtime, 0, ICON_VIEW3D, 0, 0, 19, UI_UNIT_Y,&md->mode, 0, 0, 1, 0, "Enable modifier during interactive display"); + if (mti->flags&eModifierTypeFlag_SupportsEditmode) { + uiDefIconButBitI(block, TOG, eModifierMode_Editmode, 0, ICON_EDITMODE_HLT, 0, 0, 19, UI_UNIT_Y,&md->mode, 0, 0, 1, 0, "Enable modifier during Editmode (only if enabled for display)"); + } + } + uiBlockEndAlign(block); + + /* XXX uiBlockSetEmboss(block, UI_EMBOSSR); */ + + if (ob->type==OB_MESH && modifier_couldBeCage(md) && index<=lastCageIndex) { + int icon; //, color; + + if (index==cageIndex) { + // XXX color = TH_BUT_SETTING; + icon = VICON_EDITMODE_HLT; + } else if (index<cageIndex) { + // XXX color = TH_BUT_NEUTRAL; + icon = VICON_EDITMODE_DEHLT; + } else { + // XXX color = TH_BUT_NEUTRAL; + icon = ICON_BLANK1; + } + /* XXX uiBlockSetCol(block, color); */ + but = uiDefIconBut(block, BUT, 0, icon, 0, 0, 16, 16, NULL, 0.0, 0.0, 0.0, 0.0, "Apply modifier to editing cage during Editmode"); + uiButSetFunc(but, modifiers_setOnCage, ob, md); + /* XXX uiBlockSetCol(block, TH_AUTO); */ + } + } + + subrow= uiLayoutRow(row, 0); + uiLayoutSetAlignment(subrow, UI_LAYOUT_ALIGN_RIGHT); + + if(!isVirtual) { + /* XXX uiBlockSetCol(block, TH_BUT_ACTION); */ + + but = uiDefIconBut(block, BUT, 0, VICON_MOVE_UP, 0, 0, 16, 16, NULL, 0.0, 0.0, 0.0, 0.0, "Move modifier up in stack"); + uiButSetFunc(but, modifiers_moveUp, ob, md); + + but = uiDefIconBut(block, BUT, 0, VICON_MOVE_DOWN, 0, 0, 16, 16, NULL, 0.0, 0.0, 0.0, 0.0, "Move modifier down in stack"); + uiButSetFunc(but, modifiers_moveDown, ob, md); + + uiBlockSetEmboss(block, UI_EMBOSSN); + + if(modifier_can_delete(md)) { + but = uiDefIconBut(block, BUT, 0, VICON_X, 0, 0, 16, 16, NULL, 0.0, 0.0, 0.0, 0.0, "Delete modifier"); + uiButSetFunc(but, modifiers_del, ob, md); + } + /* XXX uiBlockSetCol(block, TH_AUTO); */ + } + + uiBlockSetEmboss(block, UI_EMBOSS); + + if(!isVirtual && (md->mode&eModifierMode_Expanded)) { + uiLayout *box; + + box= uiLayoutBox(column); + row= uiLayoutRow(box, 1); + + if (!isVirtual && (md->type!=eModifierType_Collision) && (md->type!=eModifierType_Surface)) { + uiBlockSetButLock(block, object_data_is_libdata(ob), ERROR_LIBDATA_MESSAGE); /* only here obdata, the rest of modifiers is ob level */ + + if (md->type==eModifierType_ParticleSystem) { + ParticleSystem *psys= ((ParticleSystemModifierData *)md)->psys; + + if(!(G.f & G_PARTICLEEDIT)) { + if(ELEM3(psys->part->draw_as, PART_DRAW_PATH, PART_DRAW_GR, PART_DRAW_OB) && psys->pathcache) { + but = uiDefBut(block, BUT, 0, "Convert", 0,0,60,19, 0, 0, 0, 0, 0, "Convert the current particles to a mesh object"); + uiButSetFunc(but, modifiers_convertParticles, ob, md); + } + } + } + else{ + but = uiDefBut(block, BUT, 0, "Apply", 0,0,60,19, 0, 0, 0, 0, 0, "Apply the current modifier and remove from the stack"); + uiButSetFunc(but, modifiers_applyModifier, ob, md); + } + + uiBlockClearButLock(block); + uiBlockSetButLock(block, ob && ob->id.lib, ERROR_LIBDATA_MESSAGE); + + if (md->type!=eModifierType_Fluidsim && md->type!=eModifierType_Softbody && md->type!=eModifierType_ParticleSystem && (md->type!=eModifierType_Cloth)) { + but = uiDefBut(block, BUT, 0, "Copy", 0,0,60,19, 0, 0, 0, 0, 0, "Duplicate the current modifier at the same position in the stack"); + uiButSetFunc(but, modifiers_copyModifier, ob, md); + } + } + + result= uiLayoutColumn(box, 0); + block= uiLayoutFreeBlock(box); + } + + if (md->error) { + row = uiLayoutRow(uiLayoutBox(column), 0); + + /* XXX uiBlockSetCol(block, color); */ + uiItemL(row, md->error, ICON_ERROR); + /* XXX uiBlockSetCol(block, TH_AUTO); */ + } + + return result; +} + +uiLayout *uiTemplateModifier(uiLayout *layout, PointerRNA *ptr) +{ + Object *ob; + ModifierData *md, *vmd; + int i, lastCageIndex, cageIndex; + + /* verify we have valid data */ + if(!RNA_struct_is_a(ptr->type, &RNA_Modifier)) { + printf("uiTemplateModifier: expected modifier on object.\n"); + return NULL; + } + + ob= ptr->id.data; + md= ptr->data; + + if(!ob || !(GS(ob->id.name) == ID_OB)) { + printf("uiTemplateModifier: expected modifier on object.\n"); + return NULL; + } + + uiBlockSetButLock(uiLayoutGetBlock(layout), (ob && ob->id.lib), ERROR_LIBDATA_MESSAGE); + + /* find modifier and draw it */ + cageIndex = modifiers_getCageIndex(ob, &lastCageIndex); + + // XXX virtual modifiers are not accesible for python + vmd = modifiers_getVirtualModifierList(ob); + + for(i=0; vmd; i++, vmd=vmd->next) { + if(md == vmd) + return draw_modifier(layout, ob, md, i, cageIndex, lastCageIndex); + else if(vmd->mode&eModifierMode_Virtual) + i--; + } + + return NULL; +} + +/************************ Constraint Template *************************/ + +#include "DNA_action_types.h" +#include "DNA_constraint_types.h" + +#include "BKE_action.h" +#include "BKE_constraint.h" + +#define REDRAWIPO 1 +#define REDRAWNLA 2 +#define REDRAWBUTSOBJECT 3 +#define REDRAWACTION 4 +#define B_CONSTRAINT_TEST 5 +#define B_CONSTRAINT_CHANGETARGET 6 +#define B_CONSTRAINT_INF 7 +#define REMAKEIPO 8 +#define B_DIFF 9 + +void do_constraint_panels(bContext *C, void *arg, int event) +{ + Scene *scene= CTX_data_scene(C); + Object *ob= CTX_data_active_object(C); + + switch(event) { + case B_CONSTRAINT_TEST: + // XXX allqueue(REDRAWVIEW3D, 0); + // XXX allqueue(REDRAWBUTSOBJECT, 0); + // XXX allqueue(REDRAWBUTSEDIT, 0); + break; // no handling + case B_CONSTRAINT_INF: + /* influence; do not execute actions for 1 dag_flush */ + if (ob->pose) + ob->pose->flag |= (POSE_LOCKED|POSE_DO_UNLOCK); + break; + case B_CONSTRAINT_CHANGETARGET: + if (ob->pose) ob->pose->flag |= POSE_RECALC; // checks & sorts pose channels + DAG_scene_sort(scene); + break; + default: + break; + } + + object_test_constraints(ob); + + if(ob->pose) update_pose_constraint_flags(ob->pose); + + if(ob->type==OB_ARMATURE) DAG_object_flush_update(scene, ob, OB_RECALC_DATA|OB_RECALC_OB); + else DAG_object_flush_update(scene, ob, OB_RECALC_OB); + + // XXX allqueue(REDRAWVIEW3D, 0); + // XXX allqueue(REDRAWBUTSOBJECT, 0); + // XXX allqueue(REDRAWBUTSEDIT, 0); +} + +static void constraint_active_func(bContext *C, void *ob_v, void *con_v) +{ + ED_object_constraint_set_active(ob_v, con_v); +} + +static void del_constraint_func (bContext *C, void *ob_v, void *con_v) +{ + if(ED_object_constraint_delete(NULL, ob_v, con_v)) + ED_undo_push(C, "Delete Constraint"); +} + +static void verify_constraint_name_func (bContext *C, void *con_v, void *name_v) +{ + Object *ob= CTX_data_active_object(C); + bConstraint *con= con_v; + char oldname[32]; + + if (!con) + return; + + /* put on the stack */ + BLI_strncpy(oldname, (char *)name_v, 32); + + ED_object_constraint_rename(ob, con, oldname); + ED_object_constraint_set_active(ob, con); + // XXX allqueue(REDRAWACTION, 0); +} + +static void constraint_moveUp(bContext *C, void *ob_v, void *con_v) +{ + if(ED_object_constraint_move_up(NULL, ob_v, con_v)) + ED_undo_push(C, "Move Constraint"); +} + +static void constraint_moveDown(bContext *C, void *ob_v, void *con_v) +{ + if(ED_object_constraint_move_down(NULL, ob_v, con_v)) + ED_undo_push(C, "Move Constraint"); +} + +/* some commonly used macros in the constraints drawing code */ +#define is_armature_target(target) (target && target->type==OB_ARMATURE) +#define is_armature_owner(ob) ((ob->type == OB_ARMATURE) && (ob->flag & OB_POSEMODE)) +#define is_geom_target(target) (target && (ELEM(target->type, OB_MESH, OB_LATTICE)) ) + +/* Helper function for draw constraint - draws constraint space stuff + * This function should not be called if no menus are required + * owner/target: -1 = don't draw menu; 0= not posemode, 1 = posemode + */ +static void draw_constraint_spaceselect (uiBlock *block, bConstraint *con, short xco, short yco, short owner, short target) +{ + short tarx, ownx, iconx; + short bwidth; + short iconwidth = 20; + + /* calculate sizes and placement of menus */ + if (owner == -1) { + bwidth = 125; + tarx = 120; + ownx = 0; + } + else if (target == -1) { + bwidth = 125; + tarx = 0; + ownx = 120; + } + else { + bwidth = 100; + tarx = 85; + iconx = tarx + bwidth + 5; + ownx = tarx + bwidth + iconwidth + 10; + } + + + uiDefBut(block, LABEL, B_CONSTRAINT_TEST, "Convert:", xco, yco, 80,18, NULL, 0.0, 0.0, 0.0, 0.0, ""); + + /* Target-Space */ + if (target == 1) { + uiDefButC(block, MENU, B_CONSTRAINT_TEST, "Target Space %t|World Space %x0|Pose Space %x2|Local with Parent %x3|Local Space %x1", + tarx, yco, bwidth, 18, &con->tarspace, 0, 0, 0, 0, "Choose space that target is evaluated in"); + } + else if (target == 0) { + uiDefButC(block, MENU, B_CONSTRAINT_TEST, "Target Space %t|World Space %x0|Local (Without Parent) Space %x1", + tarx, yco, bwidth, 18, &con->tarspace, 0, 0, 0, 0, "Choose space that target is evaluated in"); + } + + if ((target != -1) && (owner != -1)) + uiDefIconBut(block, LABEL, 0, ICON_ARROW_LEFTRIGHT, + iconx, yco, 20, 20, NULL, 0.0, 0.0, 0.0, 0.0, ""); + + /* Owner-Space */ + if (owner == 1) { + uiDefButC(block, MENU, B_CONSTRAINT_TEST, "Owner Space %t|World Space %x0|Pose Space %x2|Local with Parent %x3|Local Space %x1", + ownx, yco, bwidth, 18, &con->ownspace, 0, 0, 0, 0, "Choose space that owner is evaluated in"); + } + else if (owner == 0) { + uiDefButC(block, MENU, B_CONSTRAINT_TEST, "Owner Space %t|World Space %x0|Local (Without Parent) Space %x1", + ownx, yco, bwidth, 18, &con->ownspace, 0, 0, 0, 0, "Choose space that owner is evaluated in"); + } +} + +/* draw panel showing settings for a constraint */ +static uiLayout *draw_constraint(uiLayout *layout, Object *ob, bConstraint *con) +{ + bPoseChannel *pchan= get_active_posechannel(ob); + bConstraintTypeInfo *cti; + uiBlock *block; + uiLayout *result= NULL, *col, *box, *row, *subrow; + uiBut *but; + PointerRNA ptr; + char typestr[32]; + short width = 265; + short proxy_protected, xco=0, yco=0; + int rb_col; + + /* get constraint typeinfo */ + cti= constraint_get_typeinfo(con); + if (cti == NULL) { + /* exception for 'Null' constraint - it doesn't have constraint typeinfo! */ + if (con->type == CONSTRAINT_TYPE_NULL) + strcpy(typestr, "Null"); + else + strcpy(typestr, "Unknown"); + } + else + strcpy(typestr, cti->name); + + /* determine whether constraint is proxy protected or not */ + if (proxylocked_constraints_owner(ob, pchan)) + proxy_protected= (con->flag & CONSTRAINT_PROXY_LOCAL)==0; + else + proxy_protected= 0; + + /* unless button has own callback, it adds this callback to button */ + block= uiLayoutGetBlock(layout); + uiBlockSetHandleFunc(block, do_constraint_panels, NULL); + uiBlockSetFunc(block, constraint_active_func, ob, con); + + RNA_pointer_create(&ob->id, &RNA_Constraint, con, &ptr); + + col= uiLayoutColumn(layout, 1); + uiLayoutSetContextPointer(col, "constraint", &ptr); + + box= uiLayoutBox(col); + row= uiLayoutRow(box, 0); + + block= uiLayoutFreeBlock(box); + + subrow= uiLayoutRow(row, 0); + uiLayoutSetAlignment(subrow, UI_LAYOUT_ALIGN_LEFT); + + /* Draw constraint header */ + uiBlockSetEmboss(block, UI_EMBOSSN); + + /* rounded header */ + rb_col= (con->flag & CONSTRAINT_ACTIVE)?50:20; + + /* open/close */ + uiDefIconButBitS(block, ICONTOG, CONSTRAINT_EXPAND, B_CONSTRAINT_TEST, ICON_TRIA_RIGHT, xco-10, yco, 20, 20, &con->flag, 0.0, 0.0, 0.0, 0.0, "Collapse/Expand Constraint"); + + /* name */ + if ((con->flag & CONSTRAINT_EXPAND) && (proxy_protected==0)) { + /* XXX if (con->flag & CONSTRAINT_DISABLE) + uiBlockSetCol(block, TH_REDALERT);*/ + + uiBlockSetEmboss(block, UI_EMBOSS); + + uiDefBut(block, LABEL, B_CONSTRAINT_TEST, typestr, xco+10, yco, 100, 18, NULL, 0.0, 0.0, 0.0, 0.0, ""); + + but = uiDefBut(block, TEX, B_CONSTRAINT_TEST, "", xco+120, yco, 85, 18, con->name, 0.0, 29.0, 0.0, 0.0, "Constraint name"); + uiButSetFunc(but, verify_constraint_name_func, con, NULL); + } + else { + uiBlockSetEmboss(block, UI_EMBOSSN); + + /* XXX if (con->flag & CONSTRAINT_DISABLE) + uiBlockSetCol(block, TH_REDALERT);*/ + + uiDefBut(block, LABEL, B_CONSTRAINT_TEST, typestr, xco+10, yco, 100, 18, NULL, 0.0, 0.0, 0.0, 0.0, ""); + + uiDefBut(block, LABEL, B_CONSTRAINT_TEST, con->name, xco+120, yco-1, 135, 19, NULL, 0.0, 0.0, 0.0, 0.0, ""); + } + + // XXX uiBlockSetCol(block, TH_AUTO); + + subrow= uiLayoutRow(row, 0); + uiLayoutSetAlignment(subrow, UI_LAYOUT_ALIGN_RIGHT); + + /* proxy-protected constraints cannot be edited, so hide up/down + close buttons */ + if (proxy_protected) { + uiBlockSetEmboss(block, UI_EMBOSSN); + + /* draw a ghost icon (for proxy) and also a lock beside it, to show that constraint is "proxy locked" */ + uiDefIconBut(block, BUT, B_CONSTRAINT_TEST, ICON_GHOST, xco+244, yco, 19, 19, NULL, 0.0, 0.0, 0.0, 0.0, "Proxy Protected"); + uiDefIconBut(block, BUT, B_CONSTRAINT_TEST, ICON_LOCKED, xco+262, yco, 19, 19, NULL, 0.0, 0.0, 0.0, 0.0, "Proxy Protected"); + + uiBlockSetEmboss(block, UI_EMBOSS); + } + else { + short prev_proxylock, show_upbut, show_downbut; + + /* Up/Down buttons: + * Proxy-constraints are not allowed to occur after local (non-proxy) constraints + * as that poses problems when restoring them, so disable the "up" button where + * it may cause this situation. + * + * Up/Down buttons should only be shown (or not greyed - todo) if they serve some purpose. + */ + if (proxylocked_constraints_owner(ob, pchan)) { + if (con->prev) { + prev_proxylock= (con->prev->flag & CONSTRAINT_PROXY_LOCAL) ? 0 : 1; + } + else + prev_proxylock= 0; + } + else + prev_proxylock= 0; + + show_upbut= ((prev_proxylock == 0) && (con->prev)); + show_downbut= (con->next) ? 1 : 0; + + if (show_upbut || show_downbut) { + uiBlockBeginAlign(block); + uiBlockSetEmboss(block, UI_EMBOSS); + + if (show_upbut) { + but = uiDefIconBut(block, BUT, B_CONSTRAINT_TEST, VICON_MOVE_UP, xco+width-50, yco, 16, 18, NULL, 0.0, 0.0, 0.0, 0.0, "Move constraint up in constraint stack"); + uiButSetFunc(but, constraint_moveUp, ob, con); + } + + if (show_downbut) { + but = uiDefIconBut(block, BUT, B_CONSTRAINT_TEST, VICON_MOVE_DOWN, xco+width-50+18, yco, 16, 18, NULL, 0.0, 0.0, 0.0, 0.0, "Move constraint down in constraint stack"); + uiButSetFunc(but, constraint_moveDown, ob, con); + } + uiBlockEndAlign(block); + } + + + /* Close 'button' - emboss calls here disable drawing of 'button' behind X */ + uiBlockSetEmboss(block, UI_EMBOSSN); + + but = uiDefIconBut(block, BUT, B_CONSTRAINT_CHANGETARGET, ICON_X, xco+262, yco, 19, 19, NULL, 0.0, 0.0, 0.0, 0.0, "Delete constraint"); + uiButSetFunc(but, del_constraint_func, ob, con); + + uiBlockSetEmboss(block, UI_EMBOSS); + } + + /* Set but-locks for protected settings (magic numbers are used here!) */ + if (proxy_protected) + uiBlockSetButLock(block, 1, "Cannot edit Proxy-Protected Constraint"); + + /* Draw constraint data */ + if ((con->flag & CONSTRAINT_EXPAND) == 0) { + (yco) -= 21; + } + else { + box= uiLayoutBox(col); + block= uiLayoutFreeBlock(box); + + switch (con->type) { +#ifndef DISABLE_PYTHON + case CONSTRAINT_TYPE_PYTHON: + { + bPythonConstraint *data = con->data; + bConstraintTarget *ct; + // uiBut *but2; + int tarnum, theight; + // static int pyconindex=0; + // char *menustr; + + theight = (data->tarnum)? (data->tarnum * 38) : (38); + + uiDefBut(block, LABEL, B_CONSTRAINT_TEST, "Script:", xco+60, yco-24, 55, 18, NULL, 0.0, 0.0, 0.0, 0.0, ""); + + /* do the scripts menu */ + /* XXX menustr = buildmenu_pyconstraints(data->text, &pyconindex); + but2 = uiDefButI(block, MENU, B_CONSTRAINT_TEST, menustr, + xco+120, yco-24, 150, 20, &pyconindex, + 0, 0, 0, 0, "Set the Script Constraint to use"); + uiButSetFunc(but2, validate_pyconstraint_cb, data, &pyconindex); + MEM_freeN(menustr); */ + + /* draw target(s) */ + if (data->flag & PYCON_USETARGETS) { + /* Draw target parameters */ + for (ct=data->targets.first, tarnum=1; ct; ct=ct->next, tarnum++) { + char tarstr[32]; + short yoffset= ((tarnum-1) * 38); + + /* target label */ + sprintf(tarstr, "Target %d:", tarnum); + uiDefBut(block, LABEL, B_CONSTRAINT_TEST, tarstr, xco+45, yco-(48+yoffset), 100, 18, NULL, 0.0, 0.0, 0.0, 0.0, ""); + + /* target space-selector - per target */ + if (is_armature_target(ct->tar)) { + uiDefButS(block, MENU, B_CONSTRAINT_TEST, "Target Space %t|World Space %x0|Pose Space %x3|Local with Parent %x4|Local Space %x1", + xco+10, yco-(66+yoffset), 100, 18, &ct->space, 0, 0, 0, 0, "Choose space that target is evaluated in"); + } + else { + uiDefButS(block, MENU, B_CONSTRAINT_TEST, "Target Space %t|World Space %x0|Local (Without Parent) Space %x1", + xco+10, yco-(66+yoffset), 100, 18, &ct->space, 0, 0, 0, 0, "Choose space that target is evaluated in"); + } + + uiBlockBeginAlign(block); + /* target object */ + uiDefIDPoinBut(block, test_obpoin_but, ID_OB, B_CONSTRAINT_CHANGETARGET, "OB:", xco+120, yco-(48+yoffset), 150, 18, &ct->tar, "Target Object"); + + /* subtarget */ + if (is_armature_target(ct->tar)) { + but= uiDefBut(block, TEX, B_CONSTRAINT_CHANGETARGET, "BO:", xco+120, yco-(66+yoffset),150,18, &ct->subtarget, 0, 24, 0, 0, "Subtarget Bone"); + uiButSetCompleteFunc(but, autocomplete_bone, (void *)ct->tar); + } + else if (is_geom_target(ct->tar)) { + but= uiDefBut(block, TEX, B_CONSTRAINT_CHANGETARGET, "VG:", xco+120, yco-(66+yoffset),150,18, &ct->subtarget, 0, 24, 0, 0, "Name of Vertex Group defining 'target' points"); + uiButSetCompleteFunc(but, autocomplete_vgroup, (void *)ct->tar); + } + else { + strcpy(ct->subtarget, ""); + } + uiBlockEndAlign(block); + } + } + else { + /* Draw indication that no target needed */ + uiDefBut(block, LABEL, B_CONSTRAINT_TEST, "Target:", xco+60, yco-48, 55, 18, NULL, 0.0, 0.0, 0.0, 0.0, ""); + uiDefBut(block, LABEL, B_CONSTRAINT_TEST, "Not Applicable", xco+120, yco-48, 150, 18, NULL, 0.0, 0.0, 0.0, 0.0, ""); + } + + /* settings */ + uiBlockBeginAlign(block); + but=uiDefBut(block, BUT, B_CONSTRAINT_TEST, "Options", xco, yco-(52+theight), (width/2),18, NULL, 0, 24, 0, 0, "Change some of the constraint's settings."); + // XXX uiButSetFunc(but, BPY_pyconstraint_settings, data, NULL); + + but=uiDefBut(block, BUT, B_CONSTRAINT_TEST, "Refresh", xco+((width/2)+10), yco-(52+theight), (width/2),18, NULL, 0, 24, 0, 0, "Force constraint to refresh it's settings"); + uiBlockEndAlign(block); + + /* constraint space settings */ + draw_constraint_spaceselect(block, con, xco, yco-(73+theight), is_armature_owner(ob), -1); + } + break; +#endif /* DISABLE_PYTHON */ + /*case CONSTRAINT_TYPE_CHILDOF: + { + // Inverse options + uiBlockBeginAlign(block); + but=uiDefBut(block, BUT, B_CONSTRAINT_TEST, "Set Offset", xco, yco-151, (width/2),18, NULL, 0, 24, 0, 0, "Calculate current Parent-Inverse Matrix (i.e. restore offset from parent)"); + // XXX uiButSetFunc(but, childof_const_setinv, con, NULL); + + but=uiDefBut(block, BUT, B_CONSTRAINT_TEST, "Clear Offset", xco+((width/2)+10), yco-151, (width/2),18, NULL, 0, 24, 0, 0, "Clear Parent-Inverse Matrix (i.e. clear offset from parent)"); + // XXX uiButSetFunc(but, childof_const_clearinv, con, NULL); + uiBlockEndAlign(block); + } + break; + */ + + /*case CONSTRAINT_TYPE_RIGIDBODYJOINT: + { + if (data->type==CONSTRAINT_RB_GENERIC6DOF) { + // Draw Pairs of LimitToggle+LimitValue + uiBlockBeginAlign(block); + uiDefButBitS(block, TOG, 1, B_CONSTRAINT_TEST, "LinMinX", xco, yco-offsetY, togButWidth, 18, &data->flag, 0, 24, 0, 0, "Use minimum x limit"); + uiDefButF(block, NUM, B_CONSTRAINT_TEST, "", xco+togButWidth, yco-offsetY, (textButWidth-5), 18, &(data->minLimit[0]), -extremeLin, extremeLin, 0.1,0.5,"min x limit"); + uiBlockEndAlign(block); + + uiBlockBeginAlign(block); + uiDefButBitS(block, TOG, 1, B_CONSTRAINT_TEST, "LinMaxX", xco+(width-(textButWidth-5)-togButWidth), yco-offsetY, togButWidth, 18, &data->flag, 0, 24, 0, 0, "Use maximum x limit"); + uiDefButF(block, NUM, B_CONSTRAINT_TEST, "", xco+(width-textButWidth-5), yco-offsetY, (textButWidth), 18, &(data->maxLimit[0]), -extremeLin, extremeLin, 0.1,0.5,"max x limit"); + uiBlockEndAlign(block); + + offsetY += 20; + uiBlockBeginAlign(block); + uiDefButBitS(block, TOG, 2, B_CONSTRAINT_TEST, "LinMinY", xco, yco-offsetY, togButWidth, 18, &data->flag, 0, 24, 0, 0, "Use minimum y limit"); + uiDefButF(block, NUM, B_CONSTRAINT_TEST, "", xco+togButWidth, yco-offsetY, (textButWidth-5), 18, &(data->minLimit[1]), -extremeLin, extremeLin, 0.1,0.5,"min y limit"); + uiBlockEndAlign(block); + + uiBlockBeginAlign(block); + uiDefButBitS(block, TOG, 2, B_CONSTRAINT_TEST, "LinMaxY", xco+(width-(textButWidth-5)-togButWidth), yco-offsetY, togButWidth, 18, &data->flag, 0, 24, 0, 0, "Use maximum y limit"); + uiDefButF(block, NUM, B_CONSTRAINT_TEST, "", xco+(width-textButWidth-5), yco-offsetY, (textButWidth), 18, &(data->maxLimit[1]), -extremeLin, extremeLin, 0.1,0.5,"max y limit"); + uiBlockEndAlign(block); + + offsetY += 20; + uiBlockBeginAlign(block); + uiDefButBitS(block, TOG, 4, B_CONSTRAINT_TEST, "LinMinZ", xco, yco-offsetY, togButWidth, 18, &data->flag, 0, 24, 0, 0, "Use minimum z limit"); + uiDefButF(block, NUM, B_CONSTRAINT_TEST, "", xco+togButWidth, yco-offsetY, (textButWidth-5), 18, &(data->minLimit[2]), -extremeLin, extremeLin, 0.1,0.5,"min z limit"); + uiBlockEndAlign(block); + + uiBlockBeginAlign(block); + uiDefButBitS(block, TOG, 4, B_CONSTRAINT_TEST, "LinMaxZ", xco+(width-(textButWidth-5)-togButWidth), yco-offsetY, togButWidth, 18, &data->flag, 0, 24, 0, 0, "Use maximum z limit"); + uiDefButF(block, NUM, B_CONSTRAINT_TEST, "", xco+(width-textButWidth-5), yco-offsetY, (textButWidth), 18, &(data->maxLimit[2]), -extremeLin, extremeLin, 0.1,0.5,"max z limit"); + uiBlockEndAlign(block); + offsetY += 20; + } + if ((data->type==CONSTRAINT_RB_GENERIC6DOF) || (data->type==CONSTRAINT_RB_CONETWIST)) { + // Draw Pairs of LimitToggle+LimitValue / + uiBlockBeginAlign(block); + uiDefButBitS(block, TOG, 8, B_CONSTRAINT_TEST, "AngMinX", xco, yco-offsetY, togButWidth, 18, &data->flag, 0, 24, 0, 0, "Use minimum x limit"); + uiDefButF(block, NUM, B_CONSTRAINT_TEST, "", xco+togButWidth, yco-offsetY, (textButWidth-5), 18, &(data->minLimit[3]), -extremeAngX, extremeAngX, 0.1,0.5,"min x limit"); + uiBlockEndAlign(block); + uiBlockBeginAlign(block); + uiDefButBitS(block, TOG, 8, B_CONSTRAINT_TEST, "AngMaxX", xco+(width-(textButWidth-5)-togButWidth), yco-offsetY, togButWidth, 18, &data->flag, 0, 24, 0, 0, "Use maximum x limit"); + uiDefButF(block, NUM, B_CONSTRAINT_TEST, "", xco+(width-textButWidth-5), yco-offsetY, (textButWidth), 18, &(data->maxLimit[3]), -extremeAngX, extremeAngX, 0.1,0.5,"max x limit"); + uiBlockEndAlign(block); + + offsetY += 20; + uiBlockBeginAlign(block); + uiDefButBitS(block, TOG, 16, B_CONSTRAINT_TEST, "AngMinY", xco, yco-offsetY, togButWidth, 18, &data->flag, 0, 24, 0, 0, "Use minimum y limit"); + uiDefButF(block, NUM, B_CONSTRAINT_TEST, "", xco+togButWidth, yco-offsetY, (textButWidth-5), 18, &(data->minLimit[4]), -extremeAngY, extremeAngY, 0.1,0.5,"min y limit"); + uiBlockEndAlign(block); + + uiBlockBeginAlign(block); + uiDefButBitS(block, TOG, 16, B_CONSTRAINT_TEST, "AngMaxY", xco+(width-(textButWidth-5)-togButWidth), yco-offsetY, togButWidth, 18, &data->flag, 0, 24, 0, 0, "Use maximum y limit"); + uiDefButF(block, NUM, B_CONSTRAINT_TEST, "", xco+(width-textButWidth-5), yco-offsetY, (textButWidth), 18, &(data->maxLimit[4]), -extremeAngY, extremeAngY, 0.1,0.5,"max y limit"); + uiBlockEndAlign(block); + + offsetY += 20; + uiBlockBeginAlign(block); + uiDefButBitS(block, TOG, 32, B_CONSTRAINT_TEST, "AngMinZ", xco, yco-offsetY, togButWidth, 18, &data->flag, 0, 24, 0, 0, "Use minimum z limit"); + uiDefButF(block, NUM, B_CONSTRAINT_TEST, "", xco+togButWidth, yco-offsetY, (textButWidth-5), 18, &(data->minLimit[5]), -extremeAngZ, extremeAngZ, 0.1,0.5,"min z limit"); + uiBlockEndAlign(block); + + uiBlockBeginAlign(block); + uiDefButBitS(block, TOG, 32, B_CONSTRAINT_TEST, "AngMaxZ", xco+(width-(textButWidth-5)-togButWidth), yco-offsetY, togButWidth, 18, &data->flag, 0, 24, 0, 0, "Use maximum z limit"); + uiDefButF(block, NUM, B_CONSTRAINT_TEST, "", xco+(width-textButWidth-5), yco-offsetY, (textButWidth), 18, &(data->maxLimit[5]), -extremeAngZ, extremeAngZ, 0.1,0.5,"max z limit"); + uiBlockEndAlign(block); + } + + } + break; + */ + + case CONSTRAINT_TYPE_NULL: + { + uiItemL(box, "", 0); + } + break; + default: + result= box; + break; + } + } + + /* clear any locks set up for proxies/lib-linking */ + uiBlockClearButLock(block); + + return result; +} + +uiLayout *uiTemplateConstraint(uiLayout *layout, PointerRNA *ptr) +{ + Object *ob; + bConstraint *con; + + /* verify we have valid data */ + if(!RNA_struct_is_a(ptr->type, &RNA_Constraint)) { + printf("uiTemplateConstraint: expected constraint on object.\n"); + return NULL; + } + + ob= ptr->id.data; + con= ptr->data; + + if(!ob || !(GS(ob->id.name) == ID_OB)) { + printf("uiTemplateConstraint: expected constraint on object.\n"); + return NULL; + } + + uiBlockSetButLock(uiLayoutGetBlock(layout), (ob && ob->id.lib), ERROR_LIBDATA_MESSAGE); + + /* hrms, the temporal constraint should not draw! */ + if(con->type==CONSTRAINT_TYPE_KINEMATIC) { + bKinematicConstraint *data= con->data; + if(data->flag & CONSTRAINT_IK_TEMP) + return NULL; + } + + return draw_constraint(layout, ob, con); +} + +/************************* Group Template ***************************/ + +#if 0 +static void do_add_groupmenu(void *arg, int event) +{ + Object *ob= OBACT; + + if(ob) { + + if(event== -1) { + Group *group= add_group( "Group" ); + add_to_group(group, ob); + } + else + add_to_group(BLI_findlink(&G.main->group, event), ob); + + ob->flag |= OB_FROMGROUP; + BASACT->flag |= OB_FROMGROUP; + allqueue(REDRAWBUTSOBJECT, 0); + allqueue(REDRAWVIEW3D, 0); + } +} + +static uiBlock *add_groupmenu(void *arg_unused) +{ + uiBlock *block; + Group *group; + short xco=0, yco= 0, index=0; + char str[32]; + + block= uiNewBlock(&curarea->uiblocks, "add_constraintmenu", UI_EMBOSSP, UI_HELV, curarea->win); + uiBlockSetButmFunc(block, do_add_groupmenu, NULL); + + uiDefBut(block, BUTM, B_NOP, "ADD NEW", 0, 20, 160, 19, NULL, 0.0, 0.0, 1, -1, ""); + for(group= G.main->group.first; group; group= group->id.next, index++) { + + /*if(group->id.lib) strcpy(str, "L ");*/ /* we cant allow adding objects inside linked groups, it wont be saved anyway */ + if(group->id.lib==0) { + strcpy(str, " "); + strcat(str, group->id.name+2); + uiDefBut(block, BUTM, B_NOP, str, xco*160, -20*yco, 160, 19, NULL, 0.0, 0.0, 1, index, ""); + + yco++; + if(yco>24) { + yco= 0; + xco++; + } + } + } + + uiTextBoundsBlock(block, 50); + uiBlockSetDirection(block, UI_DOWN); + + return block; +} + +static void group_ob_rem(void *gr_v, void *ob_v) +{ + Object *ob= OBACT; + + if(rem_from_group(gr_v, ob) && find_group(ob, NULL)==NULL) { + ob->flag &= ~OB_FROMGROUP; + BASACT->flag &= ~OB_FROMGROUP; + } + allqueue(REDRAWBUTSOBJECT, 0); + allqueue(REDRAWVIEW3D, 0); + +} + +static void group_local(void *gr_v, void *unused) +{ + Group *group= gr_v; + + group->id.lib= NULL; + + allqueue(REDRAWBUTSOBJECT, 0); + allqueue(REDRAWVIEW3D, 0); + +} + +uiLayout *uiTemplateGroup(uiLayout *layout, Object *ob, Group *group) +{ + uiSetButLock(1, NULL); + uiDefBlockBut(block, add_groupmenu, NULL, "Add to Group", 10,150,150,20, "Add Object to a new Group"); + + /* all groups */ + if(group->id.lib) { + uiLayoutRow() + uiBlockBeginAlign(block); + uiSetButLock(GET_INT_FROM_POINTER(group->id.lib), ERROR_LIBDATA_MESSAGE); /* We cant actually use this button */ + uiDefBut(block, TEX, B_IDNAME, "GR:", 10, 120-yco, 100, 20, group->id.name+2, 0.0, 21.0, 0, 0, "Displays Group name. Click to change."); + uiClearButLock(); + + but= uiDefIconBut(block, BUT, B_NOP, ICON_PARLIB, 110, 120-yco, 20, 20, NULL, 0.0, 0.0, 0.0, 0.0, "Make Group local"); + uiButSetFunc(but, group_local, group, NULL); + uiBlockEndAlign(block); + } else { + but = uiDefBut(block, TEX, B_IDNAME, "GR:", 10, 120-yco, 120, 20, group->id.name+2, 0.0, 21.0, 0, 0, "Displays Group name. Click to change."); + uiButSetFunc(but, test_idbutton_cb, group->id.name, NULL); + } + + xco = 290; + if(group->id.lib==0) { /* cant remove objects from linked groups */ + but = uiDefIconBut(block, BUT, B_NOP, VICON_X, xco, 120-yco, 20, 20, NULL, 0.0, 0.0, 0.0, 0.0, "Remove Group membership"); + uiButSetFunc(but, group_ob_rem, group, ob); + } +} +#endif + +/************************* Preview Template ***************************/ + +#include "DNA_material_types.h" + +#define B_MATPRV 1 + + +static void do_preview_buttons(bContext *C, void *arg, int event) +{ + switch(event) { + case B_MATPRV: + WM_event_add_notifier(C, NC_MATERIAL|ND_SHADING, arg); + break; + } +} + +void uiTemplatePreview(uiLayout *layout, ID *id) +{ + uiLayout *row, *col; + uiBlock *block; + Material *ma; + + if(!id || !ELEM4(GS(id->name), ID_MA, ID_TE, ID_WO, ID_LA)) { + printf("uiTemplatePreview: expected ID of type material, texture, lamp or world.\n"); + return; + } + + block= uiLayoutGetBlock(layout); + + row= uiLayoutRow(layout, 0); + + col= uiLayoutColumn(row, 0); + uiLayoutSetKeepAspect(col, 1); + + uiDefBut(block, BUT_EXTRA, 0, "", 0, 0, UI_UNIT_X*6, UI_UNIT_Y*6, id, 0.0, 0.0, 0, 0, ""); + uiBlockSetDrawExtraFunc(block, ED_preview_draw); + + uiBlockSetHandleFunc(block, do_preview_buttons, NULL); + + if(id) { + if(GS(id->name) == ID_MA) { + ma= (Material*)id; + + uiLayoutColumn(row, 1); + + uiDefIconButC(block, ROW, B_MATPRV, ICON_MATPLANE, 0, 0,UI_UNIT_X*1.5,UI_UNIT_Y, &(ma->pr_type), 10, MA_FLAT, 0, 0, "Preview type: Flat XY plane"); + uiDefIconButC(block, ROW, B_MATPRV, ICON_MATSPHERE, 0, 0,UI_UNIT_X*1.5,UI_UNIT_Y, &(ma->pr_type), 10, MA_SPHERE, 0, 0, "Preview type: Sphere"); + uiDefIconButC(block, ROW, B_MATPRV, ICON_MATCUBE, 0, 0,UI_UNIT_X*1.5,UI_UNIT_Y, &(ma->pr_type), 10, MA_CUBE, 0, 0, "Preview type: Cube"); + uiDefIconButC(block, ROW, B_MATPRV, ICON_MONKEY, 0, 0,UI_UNIT_X*1.5,UI_UNIT_Y, &(ma->pr_type), 10, MA_MONKEY, 0, 0, "Preview type: Monkey"); + uiDefIconButC(block, ROW, B_MATPRV, ICON_HAIR, 0, 0,UI_UNIT_X*1.5,UI_UNIT_Y, &(ma->pr_type), 10, MA_HAIR, 0, 0, "Preview type: Hair strands"); + uiDefIconButC(block, ROW, B_MATPRV, ICON_MATSPHERE, 0, 0,UI_UNIT_X*1.5,UI_UNIT_Y, &(ma->pr_type), 10, MA_SPHERE_A, 0, 0, "Preview type: Large sphere with sky"); + } + } +} + +/********************** ColorRamp Template **************************/ + +void uiTemplateColorRamp(uiLayout *layout, ColorBand *coba, int expand) +{ + uiBlock *block; + rctf rect; + + if(coba) { + rect.xmin= 0; rect.xmax= 200; + rect.ymin= 0; rect.ymax= 190; + + block= uiLayoutFreeBlock(layout); + colorband_buttons(block, coba, &rect, !expand); + } +} + +/********************* CurveMapping Template ************************/ + +#include "DNA_color_types.h" + +void uiTemplateCurveMapping(uiLayout *layout, CurveMapping *cumap, int type) +{ + uiBlock *block; + rctf rect; + + if(cumap) { + rect.xmin= 0; rect.xmax= 200; + rect.ymin= 0; rect.ymax= 190; + + block= uiLayoutFreeBlock(layout); + curvemap_buttons(block, cumap, type, 0, 0, &rect); + } +} + diff --git a/source/blender/editors/interface/interface_utils.c b/source/blender/editors/interface/interface_utils.c new file mode 100644 index 00000000000..fa7de1151a3 --- /dev/null +++ b/source/blender/editors/interface/interface_utils.c @@ -0,0 +1,1047 @@ +/** + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2009 Blender Foundation. + * All rights reserved. + * + * Contributor(s): Blender Foundation + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include <math.h> +#include <stdlib.h> +#include <string.h> + +#include "MEM_guardedalloc.h" + +#include "DNA_action_types.h" +#include "DNA_color_types.h" +#include "DNA_listBase.h" +#include "DNA_material_types.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" +#include "DNA_texture_types.h" +#include "DNA_windowmanager_types.h" + +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_idprop.h" +#include "BKE_library.h" +#include "BKE_main.h" +#include "BKE_texture.h" +#include "BKE_utildefines.h" + +#include "RNA_access.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "ED_screen.h" +#include "ED_util.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "interface_intern.h" + +#define DEF_BUT_WIDTH 150 +#define DEF_ICON_BUT_WIDTH 20 +#define DEF_BUT_HEIGHT 20 + +/*************************** RNA Utilities ******************************/ + +uiBut *uiDefAutoButR(uiBlock *block, PointerRNA *ptr, PropertyRNA *prop, int index, char *name, int icon, int x1, int y1, int x2, int y2) +{ + uiBut *but=NULL; + const char *propname= RNA_property_identifier(prop); + int arraylen= RNA_property_array_length(prop); + + switch(RNA_property_type(prop)) { + case PROP_BOOLEAN: { + int value, length; + + if(arraylen && index == -1) + return NULL; + + length= RNA_property_array_length(prop); + + if(length) + value= RNA_property_boolean_get_index(ptr, prop, index); + else + value= RNA_property_boolean_get(ptr, prop); + + if(icon && name && strcmp(name, "") == 0) + but= uiDefIconButR(block, ICONTOG, 0, icon, x1, y1, x2, y2, ptr, propname, index, 0, 0, -1, -1, NULL); + else if(icon) + but= uiDefIconTextButR(block, ICONTOG, 0, icon, name, x1, y1, x2, y2, ptr, propname, index, 0, 0, -1, -1, NULL); + else + but= uiDefButR(block, OPTION, 0, name, x1, y1, x2, y2, ptr, propname, index, 0, 0, -1, -1, NULL); + break; + } + case PROP_INT: + case PROP_FLOAT: + if(arraylen && index == -1) { + if(RNA_property_subtype(prop) == PROP_COLOR) + but= uiDefButR(block, COL, 0, name, x1, y1, x2, y2, ptr, propname, 0, 0, 0, -1, -1, NULL); + } + else if(RNA_property_subtype(prop) == PROP_PERCENTAGE) + but= uiDefButR(block, NUMSLI, 0, name, x1, y1, x2, y2, ptr, propname, index, 0, 0, -1, -1, NULL); + else + but= uiDefButR(block, NUM, 0, name, x1, y1, x2, y2, ptr, propname, index, 0, 0, -1, -1, NULL); + break; + case PROP_ENUM: + but= uiDefButR(block, MENU, 0, NULL, x1, y1, x2, y2, ptr, propname, index, 0, 0, -1, -1, NULL); + break; + case PROP_STRING: + but= uiDefButR(block, TEX, 0, name, x1, y1, x2, y2, ptr, propname, index, 0, 0, -1, -1, NULL); + break; + case PROP_POINTER: { + PointerRNA pptr; + int icon; + + pptr= RNA_property_pointer_get(ptr, prop); + if(!pptr.type) + pptr.type= RNA_property_pointer_type(ptr, prop); + icon= RNA_struct_ui_icon(pptr.type); + + but= uiDefIconTextButR(block, IDPOIN, 0, icon, name, x1, y1, x2, y2, ptr, propname, index, 0, 0, -1, -1, NULL); + break; + } + case PROP_COLLECTION: { + char text[256]; + sprintf(text, "%d items", RNA_property_collection_length(ptr, prop)); + but= uiDefBut(block, LABEL, 0, text, x1, y1, x2, y2, NULL, 0, 0, 0, 0, NULL); + uiButSetFlag(but, UI_BUT_DISABLED); + break; + } + default: + but= NULL; + break; + } + + return but; +} + +void uiDefAutoButsRNA(const bContext *C, uiLayout *layout, PointerRNA *ptr) +{ + CollectionPropertyIterator iter; + PropertyRNA *iterprop, *prop; + uiLayout *split; + char *name; + + uiItemL(layout, (char*)RNA_struct_ui_name(ptr->type), 0); + + iterprop= RNA_struct_iterator_property(ptr->type); + RNA_property_collection_begin(ptr, iterprop, &iter); + + for(; iter.valid; RNA_property_collection_next(&iter)) { + prop= iter.ptr.data; + + if(strcmp(RNA_property_identifier(prop), "rna_type") == 0) + continue; + + split = uiLayoutSplit(layout, 0.5f); + + name= (char*)RNA_property_ui_name(prop); + + uiItemL(uiLayoutColumn(split, 0), name, 0); + uiItemFullR(uiLayoutColumn(split, 0), "", 0, ptr, prop, -1, 0, 0, 0, 0); + } + + RNA_property_collection_end(&iter); +} + +/* temp call, single collumn, test for toolbar only */ +void uiDefAutoButsRNA_single(const bContext *C, uiLayout *layout, PointerRNA *ptr) +{ + CollectionPropertyIterator iter; + PropertyRNA *iterprop, *prop; + uiLayout *col; + char *name; + + uiItemL(layout, (char*)RNA_struct_ui_name(ptr->type), 0); + + iterprop= RNA_struct_iterator_property(ptr->type); + RNA_property_collection_begin(ptr, iterprop, &iter); + + for(; iter.valid; RNA_property_collection_next(&iter)) { + prop= iter.ptr.data; + + if(strcmp(RNA_property_identifier(prop), "rna_type") == 0) + continue; + + name= (char*)RNA_property_ui_name(prop); + col= uiLayoutColumn(layout, 1); + uiItemL(col, name, 0); + + /* temp hack to show normal button for spin/screw */ + if(strcmp(name, "Axis")==0) { + uiDefButR(uiLayoutGetBlock(layout), BUT_NORMAL, 0, name, 0, 0, 100, 100, ptr, "axis", -1, 0, 0, -1, -1, NULL); + } + else uiItemFullR(col, "", 0, ptr, prop, -1, 0, 0, 0, 0); + } + + RNA_property_collection_end(&iter); +} + +/***************************** ID Utilities *******************************/ + +typedef struct uiIDPoinParams { + uiIDPoinFunc func; + ID *id; + short id_code; + short browsenr; +} uiIDPoinParams; + +static void idpoin_cb(bContext *C, void *arg_params, void *arg_event) +{ + Main *bmain; + ListBase *lb; + uiIDPoinParams *params= (uiIDPoinParams*)arg_params; + uiIDPoinFunc func= params->func; + ID *id= params->id, *idtest; + int nr, event= GET_INT_FROM_POINTER(arg_event); + + bmain= CTX_data_main(C); + lb= wich_libbase(bmain, params->id_code); + + if(event == UI_ID_BROWSE && params->browsenr == 32767) + event= UI_ID_ADD_NEW; + else if(event == UI_ID_BROWSE && params->browsenr == 32766) + event= UI_ID_OPEN; + + switch(event) { + case UI_ID_RENAME: + if(id) test_idbutton(id->name+2); + else return; + break; + case UI_ID_BROWSE: { + /* ID can be NULL, if nothing was assigned yet */ + if(lb->first==NULL) return; + + if(params->browsenr== -2) { + /* XXX implement or find a replacement (ID can be NULL!) + * activate_databrowse((ID *)G.buts->lockpoin, GS(id->name), 0, B_MESHBROWSE, ¶ms->browsenr, do_global_buttons); */ + return; + } + if(params->browsenr < 0) + return; + + for(idtest=lb->first, nr=1; idtest; idtest=idtest->next, nr++) { + if(nr==params->browsenr) { + if(id == idtest) + return; + + id= idtest; + + break; + } + } + break; + } + case UI_ID_DELETE: + id= NULL; + break; + case UI_ID_FAKE_USER: + if(id) { + if(id->flag & LIB_FAKEUSER) id->us++; + else id->us--; + } + else return; + break; + case UI_ID_PIN: + break; + case UI_ID_ADD_NEW: + break; + case UI_ID_OPEN: + break; + case UI_ID_ALONE: + if(!id || id->us < 1) + return; + break; + case UI_ID_LOCAL: + if(!id || id->us < 1) + return; + break; + case UI_ID_AUTO_NAME: + break; + } + + if(func) + func(C, id, event); +} + +int uiDefIDPoinButs(uiBlock *block, Main *bmain, ID *parid, ID *id, int id_code, short *pin_p, int x, int y, uiIDPoinFunc func, int events) +{ + ListBase *lb; + uiBut *but; + uiIDPoinParams *params, *dup_params; + char *str=NULL, str1[10]; + int len, add_addbutton=0; + + /* setup struct that we will pass on with the buttons */ + params= MEM_callocN(sizeof(uiIDPoinParams), "uiIDPoinParams"); + params->id= id; + params->id_code= id_code; + params->func= func; + + lb= wich_libbase(bmain, id_code); + + /* create buttons */ + uiBlockBeginAlign(block); + + /* XXX solve? + if(id && id->us>1) + uiBlockSetCol(block, TH_BUT_SETTING1); + + if((events & UI_ID_PIN) && *pin_p) + uiBlockSetCol(block, TH_BUT_SETTING2); + */ + + /* pin button */ + if(id && (events & UI_ID_PIN)) { + but= uiDefIconButS(block, ICONTOG, (events & UI_ID_PIN), ICON_KEY_DEHLT, x, y ,DEF_ICON_BUT_WIDTH,DEF_BUT_HEIGHT, pin_p, 0, 0, 0, 0, "Keeps this view displaying the current data regardless of what object is selected"); + uiButSetNFunc(but, idpoin_cb, MEM_dupallocN(params), SET_INT_IN_POINTER(UI_ID_PIN)); + x+= DEF_ICON_BUT_WIDTH; + } + + /* browse menu */ + if(events & UI_ID_BROWSE) { + char *extrastr= NULL; + + if(ELEM4(id_code, ID_MA, ID_TE, ID_BR, ID_PA)) + add_addbutton= 1; + + if(ELEM8(id_code, ID_SCE, ID_SCR, ID_MA, ID_TE, ID_WO, ID_IP, ID_AC, ID_BR) || id_code == ID_PA) + extrastr= "ADD NEW %x 32767"; + else if(id_code==ID_TXT) + extrastr= "OPEN NEW %x 32766 |ADD NEW %x 32767"; + else if(id_code==ID_SO) + extrastr= "OPEN NEW %x 32766"; + + /* XXX should be moved out of this function + uiBlockSetButLock(block, G.scene->id.lib!=0, "Can't edit external libdata"); + if( id_code==ID_SCE || id_code==ID_SCR ) uiBlockClearButLock(block); */ + + /* XXX should be moved out of this function + if(curarea->spacetype==SPACE_BUTS) + uiBlockSetButLock(block, id_code!=ID_SCR && G.obedit!=0 && G.buts->mainb==CONTEXT_EDITING, "Cannot perform in EditMode"); */ + + if(parid) + uiBlockSetButLock(block, parid->lib!=0, "Can't edit external libdata"); + + if(lb) { + if(id_code!=ID_IM || (events & UI_ID_BROWSE_RENDER)) + IDnames_to_pupstring(&str, NULL, extrastr, lb, id, ¶ms->browsenr); + else + IMAnames_to_pupstring(&str, NULL, extrastr, lb, id, ¶ms->browsenr); + } + + dup_params= MEM_dupallocN(params); + but= uiDefButS(block, MENU, 0, str, x, y, DEF_ICON_BUT_WIDTH, DEF_BUT_HEIGHT, &dup_params->browsenr, 0, 0, 0, 0, "Browse existing choices, or add new"); + uiButSetNFunc(but, idpoin_cb, dup_params, SET_INT_IN_POINTER(UI_ID_BROWSE)); + x+= DEF_ICON_BUT_WIDTH; + + uiBlockClearButLock(block); + + MEM_freeN(str); + } + + /* text button with name */ + if(id) { + /* XXX solve? + if(id->us > 1) + uiBlockSetCol(block, TH_BUT_SETTING1); + */ + /* pinned data? + if((events & UI_ID_PIN) && *pin_p) + uiBlockSetCol(block, TH_BUT_SETTING2); + */ + /* redalert overrides pin color + if(id->us<=0) + uiBlockSetCol(block, TH_REDALERT); + */ + uiBlockSetButLock(block, id->lib!=0, "Can't edit external libdata"); + + /* name button */ + text_idbutton(id, str1); + + if(GS(id->name)==ID_IP) len= 110; + else if((y) && (GS(id->name)==ID_AC)) len= 100; // comes from button panel (poselib) + else if(y) len= 140; // comes from button panel + else len= 120; + + but= uiDefBut(block, TEX, 0, str1,x, y, (short)len, DEF_BUT_HEIGHT, id->name+2, 0.0, 21.0, 0, 0, "Displays current Datablock name. Click to change."); + uiButSetNFunc(but, idpoin_cb, MEM_dupallocN(params), SET_INT_IN_POINTER(UI_ID_RENAME)); + + x+= len; + + uiBlockClearButLock(block); + + /* lib make local button */ + if(id->lib) { + if(id->flag & LIB_INDIRECT) uiDefIconBut(block, BUT, 0, 0 /* XXX ICON_DATALIB */,x,y,DEF_ICON_BUT_WIDTH,DEF_BUT_HEIGHT, 0, 0, 0, 0, 0, "Indirect Library Datablock. Cannot change."); + else { + but= uiDefIconBut(block, BUT, 0, 0 /* XXX ICON_PARLIB */, x,y,DEF_ICON_BUT_WIDTH,DEF_BUT_HEIGHT, 0, 0, 0, 0, 0, + (events & UI_ID_LOCAL)? "Direct linked Library Datablock. Click to make local.": "Direct linked Library Datablock, cannot make local."); + uiButSetNFunc(but, idpoin_cb, MEM_dupallocN(params), SET_INT_IN_POINTER(UI_ID_ALONE)); + } + + x+= DEF_ICON_BUT_WIDTH; + } + + /* number of users / make local button */ + if((events & UI_ID_ALONE) && id->us>1) { + int butwidth; + + uiBlockSetButLock(block, (events & UI_ID_PIN) && *pin_p, "Can't make pinned data single-user"); + + sprintf(str1, "%d", id->us); + butwidth= (id->us<10)? DEF_ICON_BUT_WIDTH: DEF_ICON_BUT_WIDTH+10; + + but= uiDefBut(block, BUT, 0, str1, x, y, butwidth, DEF_BUT_HEIGHT, 0, 0, 0, 0, 0, "Displays number of users of this data. Click to make a single-user copy."); + uiButSetNFunc(but, idpoin_cb, MEM_dupallocN(params), SET_INT_IN_POINTER(UI_ID_ALONE)); + x+= butwidth; + + uiBlockClearButLock(block); + } + + /* delete button */ + if(events & UI_ID_DELETE) { + uiBlockSetButLock(block, (events & UI_ID_PIN) && *pin_p, "Can't unlink pinned data"); + if(parid && parid->lib); + else { + but= uiDefIconBut(block, BUT, 0, ICON_X, x,y,DEF_ICON_BUT_WIDTH,DEF_BUT_HEIGHT, 0, 0, 0, 0, 0, "Deletes link to this Datablock"); + uiButSetNFunc(but, idpoin_cb, MEM_dupallocN(params), SET_INT_IN_POINTER(UI_ID_DELETE)); + x+= DEF_ICON_BUT_WIDTH; + } + + uiBlockClearButLock(block); + } + + /* auto name button */ + if(events & UI_ID_AUTO_NAME) { + if(parid && parid->lib); + else { + but= uiDefIconBut(block, BUT, 0, ICON_AUTO,x,y,DEF_ICON_BUT_WIDTH,DEF_BUT_HEIGHT, 0, 0, 0, 0, 0, "Generates an automatic name"); + uiButSetNFunc(but, idpoin_cb, MEM_dupallocN(params), SET_INT_IN_POINTER(UI_ID_AUTO_NAME)); + x+= DEF_ICON_BUT_WIDTH; + } + } + + /* fake user button */ + if(events & UI_ID_FAKE_USER) { + but= uiDefButBitS(block, TOG, LIB_FAKEUSER, 0, "F", x,y,DEF_ICON_BUT_WIDTH,DEF_BUT_HEIGHT, &id->flag, 0, 0, 0, 0, "Saves this datablock even if it has no users"); + uiButSetNFunc(but, idpoin_cb, MEM_dupallocN(params), SET_INT_IN_POINTER(UI_ID_FAKE_USER)); + x+= DEF_ICON_BUT_WIDTH; + } + } + /* add new button */ + else if(add_addbutton) { + if(parid) uiBlockSetButLock(block, parid->lib!=0, "Can't edit external libdata"); + dup_params= MEM_dupallocN(params); + but= uiDefButS(block, TOG, 0, "Add New", x, y, 110, DEF_BUT_HEIGHT, &dup_params->browsenr, params->browsenr, 32767.0, 0, 0, "Add new data block"); + uiButSetNFunc(but, idpoin_cb, dup_params, SET_INT_IN_POINTER(UI_ID_ADD_NEW)); + x+= 110; + } + + uiBlockEndAlign(block); + + MEM_freeN(params); + + return x; +} + +/* ****************************** default button callbacks ******************* */ +/* ************ LEGACY WARNING, only to get things work with 2.48 code! ****** */ + +void test_idbutton_cb(struct bContext *C, void *namev, void *arg2) +{ + char *name= namev; + + test_idbutton(name+2); +} + + +void test_scriptpoin_but(struct bContext *C, char *name, ID **idpp) +{ + ID *id; + + id= CTX_data_main(C)->text.first; + while(id) { + if( strcmp(name, id->name+2)==0 ) { + *idpp= id; + return; + } + id= id->next; + } + *idpp= NULL; +} + +void test_actionpoin_but(struct bContext *C, char *name, ID **idpp) +{ + ID *id; + + id= CTX_data_main(C)->action.first; + while(id) { + if( strcmp(name, id->name+2)==0 ) { + id_us_plus(id); + *idpp= id; + return; + } + id= id->next; + } + *idpp= NULL; +} + + +void test_obpoin_but(struct bContext *C, char *name, ID **idpp) +{ + ID *id; + +// XXX if(idpp == (ID **)&(emptytex.object)) { +// error("You must add a texture first"); +// *idpp= 0; +// return; +// } + + id= CTX_data_main(C)->object.first; + while(id) { + if( strcmp(name, id->name+2)==0 ) { + *idpp= id; + id_lib_extern(id); /* checks lib data, sets correct flag for saving then */ + return; + } + id= id->next; + } + *idpp= NULL; +} + +/* tests for an object of type OB_MESH */ +void test_meshobpoin_but(struct bContext *C, char *name, ID **idpp) +{ + ID *id; + + id = CTX_data_main(C)->object.first; + while(id) { + Object *ob = (Object *)id; + if(ob->type == OB_MESH && strcmp(name, id->name + 2) == 0) { + *idpp = id; + /* checks lib data, sets correct flag for saving then */ + id_lib_extern(id); + return; + } + id = id->next; + } + *idpp = NULL; +} + +void test_meshpoin_but(struct bContext *C, char *name, ID **idpp) +{ + ID *id; + + if( *idpp ) (*idpp)->us--; + + id= CTX_data_main(C)->mesh.first; + while(id) { + if( strcmp(name, id->name+2)==0 ) { + *idpp= id; + id_us_plus(id); + return; + } + id= id->next; + } + *idpp= NULL; +} + +void test_matpoin_but(struct bContext *C, char *name, ID **idpp) +{ + ID *id; + + if( *idpp ) (*idpp)->us--; + + id= CTX_data_main(C)->mat.first; + while(id) { + if( strcmp(name, id->name+2)==0 ) { + *idpp= id; + id_us_plus(id); + return; + } + id= id->next; + } + *idpp= NULL; +} + +void test_scenepoin_but(struct bContext *C, char *name, ID **idpp) +{ + ID *id; + + if( *idpp ) (*idpp)->us--; + + id= CTX_data_main(C)->scene.first; + while(id) { + if( strcmp(name, id->name+2)==0 ) { + *idpp= id; + id_us_plus(id); + return; + } + id= id->next; + } + *idpp= NULL; +} + +void test_grouppoin_but(struct bContext *C, char *name, ID **idpp) +{ + ID *id; + + if( *idpp ) (*idpp)->us--; + + id= CTX_data_main(C)->group.first; + while(id) { + if( strcmp(name, id->name+2)==0 ) { + *idpp= id; + id_us_plus(id); + return; + } + id= id->next; + } + *idpp= NULL; +} + +void test_texpoin_but(struct bContext *C, char *name, ID **idpp) +{ + ID *id; + + if( *idpp ) (*idpp)->us--; + + id= CTX_data_main(C)->tex.first; + while(id) { + if( strcmp(name, id->name+2)==0 ) { + *idpp= id; + id_us_plus(id); + return; + } + id= id->next; + } + *idpp= NULL; +} + +void test_imapoin_but(struct bContext *C, char *name, ID **idpp) +{ + ID *id; + + if( *idpp ) (*idpp)->us--; + + id= CTX_data_main(C)->image.first; + while(id) { + if( strcmp(name, id->name+2)==0 ) { + *idpp= id; + id_us_plus(id); + return; + } + id= id->next; + } + *idpp= NULL; +} + +/* autocomplete callback for buttons */ +void autocomplete_bone(struct bContext *C, char *str, void *arg_v) +{ + Object *ob= (Object *)arg_v; + + if(ob==NULL || ob->pose==NULL) return; + + /* search if str matches the beginning of name */ + if(str[0]) { + AutoComplete *autocpl= autocomplete_begin(str, 32); + bPoseChannel *pchan; + + for(pchan= ob->pose->chanbase.first; pchan; pchan= pchan->next) + autocomplete_do_name(autocpl, pchan->name); + + autocomplete_end(autocpl, str); + } +} + +/* autocomplete callback for buttons */ +void autocomplete_vgroup(struct bContext *C, char *str, void *arg_v) +{ + Object *ob= (Object *)arg_v; + + if(ob==NULL) return; + + /* search if str matches the beginning of a name */ + if(str[0]) { + AutoComplete *autocpl= autocomplete_begin(str, 32); + bDeformGroup *dg; + + for(dg= ob->defbase.first; dg; dg= dg->next) + if(dg->name!=str) + autocomplete_do_name(autocpl, dg->name); + + autocomplete_end(autocpl, str); + } +} + + +/* ----------- custom button group ---------------------- */ + +static void curvemap_buttons_zoom_in(bContext *C, void *cumap_v, void *unused) +{ + CurveMapping *cumap = cumap_v; + float d; + + /* we allow 20 times zoom */ + if( (cumap->curr.xmax - cumap->curr.xmin) > 0.04f*(cumap->clipr.xmax - cumap->clipr.xmin) ) { + d= 0.1154f*(cumap->curr.xmax - cumap->curr.xmin); + cumap->curr.xmin+= d; + cumap->curr.xmax-= d; + d= 0.1154f*(cumap->curr.ymax - cumap->curr.ymin); + cumap->curr.ymin+= d; + cumap->curr.ymax-= d; + } +} + +static void curvemap_buttons_zoom_out(bContext *C, void *cumap_v, void *unused) +{ + CurveMapping *cumap = cumap_v; + float d, d1; + + /* we allow 20 times zoom, but dont view outside clip */ + if( (cumap->curr.xmax - cumap->curr.xmin) < 20.0f*(cumap->clipr.xmax - cumap->clipr.xmin) ) { + d= d1= 0.15f*(cumap->curr.xmax - cumap->curr.xmin); + + if(cumap->flag & CUMA_DO_CLIP) + if(cumap->curr.xmin-d < cumap->clipr.xmin) + d1= cumap->curr.xmin - cumap->clipr.xmin; + cumap->curr.xmin-= d1; + + d1= d; + if(cumap->flag & CUMA_DO_CLIP) + if(cumap->curr.xmax+d > cumap->clipr.xmax) + d1= -cumap->curr.xmax + cumap->clipr.xmax; + cumap->curr.xmax+= d1; + + d= d1= 0.15f*(cumap->curr.ymax - cumap->curr.ymin); + + if(cumap->flag & CUMA_DO_CLIP) + if(cumap->curr.ymin-d < cumap->clipr.ymin) + d1= cumap->curr.ymin - cumap->clipr.ymin; + cumap->curr.ymin-= d1; + + d1= d; + if(cumap->flag & CUMA_DO_CLIP) + if(cumap->curr.ymax+d > cumap->clipr.ymax) + d1= -cumap->curr.ymax + cumap->clipr.ymax; + cumap->curr.ymax+= d1; + } +} + +static void curvemap_buttons_setclip(bContext *C, void *cumap_v, void *unused) +{ + CurveMapping *cumap = cumap_v; + + curvemapping_changed(cumap, 0); +} + +static void curvemap_buttons_delete(bContext *C, void *cumap_v, void *unused) +{ + CurveMapping *cumap = cumap_v; + + curvemap_remove(cumap->cm+cumap->cur, SELECT); + curvemapping_changed(cumap, 0); +} + +/* NOTE: this is a block-menu, needs 0 events, otherwise the menu closes */ +static uiBlock *curvemap_clipping_func(struct bContext *C, struct ARegion *ar, void *cumap_v) +{ + CurveMapping *cumap = cumap_v; + uiBlock *block; + uiBut *bt; + + block= uiBeginBlock(C, ar, "curvemap_clipping_func", UI_EMBOSS); + + /* use this for a fake extra empy space around the buttons */ + uiDefBut(block, LABEL, 0, "", -4, 16, 128, 106, NULL, 0, 0, 0, 0, ""); + + bt= uiDefButBitI(block, TOG, CUMA_DO_CLIP, 1, "Use Clipping", + 0,100,120,18, &cumap->flag, 0.0, 0.0, 10, 0, ""); + uiButSetFunc(bt, curvemap_buttons_setclip, cumap, NULL); + + uiBlockBeginAlign(block); + uiDefButF(block, NUM, 0, "Min X ", 0,74,120,18, &cumap->clipr.xmin, -100.0, cumap->clipr.xmax, 10, 0, ""); + uiDefButF(block, NUM, 0, "Min Y ", 0,56,120,18, &cumap->clipr.ymin, -100.0, cumap->clipr.ymax, 10, 0, ""); + uiDefButF(block, NUM, 0, "Max X ", 0,38,120,18, &cumap->clipr.xmax, cumap->clipr.xmin, 100.0, 10, 0, ""); + uiDefButF(block, NUM, 0, "Max Y ", 0,20,120,18, &cumap->clipr.ymax, cumap->clipr.ymin, 100.0, 10, 0, ""); + + uiBlockSetDirection(block, UI_RIGHT); + + uiEndBlock(C, block); + return block; +} + + +static void curvemap_tools_dofunc(bContext *C, void *cumap_v, int event) +{ + CurveMapping *cumap = cumap_v; + CurveMap *cuma= cumap->cm+cumap->cur; + + switch(event) { + case 0: + curvemap_reset(cuma, &cumap->clipr); + curvemapping_changed(cumap, 0); + break; + case 1: + cumap->curr= cumap->clipr; + break; + case 2: /* set vector */ + curvemap_sethandle(cuma, 1); + curvemapping_changed(cumap, 0); + break; + case 3: /* set auto */ + curvemap_sethandle(cuma, 0); + curvemapping_changed(cumap, 0); + break; + case 4: /* extend horiz */ + cuma->flag &= ~CUMA_EXTEND_EXTRAPOLATE; + curvemapping_changed(cumap, 0); + break; + case 5: /* extend extrapolate */ + cuma->flag |= CUMA_EXTEND_EXTRAPOLATE; + curvemapping_changed(cumap, 0); + break; + } + ED_region_tag_redraw(CTX_wm_region(C)); +} + +static uiBlock *curvemap_tools_func(struct bContext *C, struct ARegion *ar, void *cumap_v) +{ + uiBlock *block; + short yco= 0, menuwidth=120; + + block= uiBeginBlock(C, ar, "curvemap_tools_func", UI_EMBOSS); + uiBlockSetButmFunc(block, curvemap_tools_dofunc, cumap_v); + + uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, "Reset View", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 0, 1, ""); + uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, "Vector Handle", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 0, 2, ""); + uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, "Auto Handle", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 0, 3, ""); + uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, "Extend Horizontal", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 0, 4, ""); + uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, "Extend Extrapolated", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 0, 5, ""); + uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, "Reset Curve", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 0, 0, ""); + + uiBlockSetDirection(block, UI_RIGHT); + uiTextBoundsBlock(block, 50); + + uiEndBlock(C, block); + return block; +} + +/* still unsure how this call evolves... we use labeltype for defining what curve-channels to show */ +void curvemap_buttons(uiBlock *block, CurveMapping *cumap, char labeltype, short event, short redraw, rctf *rect) +{ + uiBut *bt; + float dx, fy= rect->ymax-18.0f; + int icon; + short xco, yco; + + yco= (short)(rect->ymax-18.0f); + + /* curve choice options + tools/settings, 8 icons + spacer */ + dx= (rect->xmax-rect->xmin)/(9.0f); + + uiBlockBeginAlign(block); + if(labeltype=='v') { /* vector */ + xco= (short)rect->xmin; + if(cumap->cm[0].curve) + uiDefButI(block, ROW, redraw, "X", xco, yco+2, dx, 16, &cumap->cur, 0.0, 0.0, 0.0, 0.0, ""); + xco= (short)(rect->xmin+1.0f*dx); + if(cumap->cm[1].curve) + uiDefButI(block, ROW, redraw, "Y", xco, yco+2, dx, 16, &cumap->cur, 0.0, 1.0, 0.0, 0.0, ""); + xco= (short)(rect->xmin+2.0f*dx); + if(cumap->cm[2].curve) + uiDefButI(block, ROW, redraw, "Z", xco, yco+2, dx, 16, &cumap->cur, 0.0, 2.0, 0.0, 0.0, ""); + } + else if(labeltype=='c') { /* color */ + xco= (short)rect->xmin; + if(cumap->cm[3].curve) + uiDefButI(block, ROW, redraw, "C", xco, yco+2, dx, 16, &cumap->cur, 0.0, 3.0, 0.0, 0.0, ""); + xco= (short)(rect->xmin+1.0f*dx); + if(cumap->cm[0].curve) + uiDefButI(block, ROW, redraw, "R", xco, yco+2, dx, 16, &cumap->cur, 0.0, 0.0, 0.0, 0.0, ""); + xco= (short)(rect->xmin+2.0f*dx); + if(cumap->cm[1].curve) + uiDefButI(block, ROW, redraw, "G", xco, yco+2, dx, 16, &cumap->cur, 0.0, 1.0, 0.0, 0.0, ""); + xco= (short)(rect->xmin+3.0f*dx); + if(cumap->cm[2].curve) + uiDefButI(block, ROW, redraw, "B", xco, yco+2, dx, 16, &cumap->cur, 0.0, 2.0, 0.0, 0.0, ""); + } + /* else no channels ! */ + uiBlockEndAlign(block); + + xco= (short)(rect->xmin+4.5f*dx); + uiBlockSetEmboss(block, UI_EMBOSSN); + bt= uiDefIconBut(block, BUT, redraw, ICON_ZOOMIN, xco, yco, dx, 14, NULL, 0.0, 0.0, 0.0, 0.0, "Zoom in"); + uiButSetFunc(bt, curvemap_buttons_zoom_in, cumap, NULL); + + xco= (short)(rect->xmin+5.25f*dx); + bt= uiDefIconBut(block, BUT, redraw, ICON_ZOOMOUT, xco, yco, dx, 14, NULL, 0.0, 0.0, 0.0, 0.0, "Zoom out"); + uiButSetFunc(bt, curvemap_buttons_zoom_out, cumap, NULL); + + xco= (short)(rect->xmin+6.0f*dx); + bt= uiDefIconBlockBut(block, curvemap_tools_func, cumap, event, ICON_MODIFIER, xco, yco, dx, 18, "Tools"); + + xco= (short)(rect->xmin+7.0f*dx); + if(cumap->flag & CUMA_DO_CLIP) icon= ICON_CLIPUV_HLT; else icon= ICON_CLIPUV_DEHLT; + bt= uiDefIconBlockBut(block, curvemap_clipping_func, cumap, event, icon, xco, yco, dx, 18, "Clipping Options"); + + xco= (short)(rect->xmin+8.0f*dx); + bt= uiDefIconBut(block, BUT, event, ICON_X, xco, yco, dx, 18, NULL, 0.0, 0.0, 0.0, 0.0, "Delete points"); + uiButSetFunc(bt, curvemap_buttons_delete, cumap, NULL); + + uiBlockSetEmboss(block, UI_EMBOSS); + + uiDefBut(block, BUT_CURVE, event, "", + rect->xmin, rect->ymin, rect->xmax-rect->xmin, fy-rect->ymin, + cumap, 0.0f, 1.0f, 0, 0, ""); +} + +#define B_BANDCOL 1 + +static int vergcband(const void *a1, const void *a2) +{ + const CBData *x1=a1, *x2=a2; + + if( x1->pos > x2->pos ) return 1; + else if( x1->pos < x2->pos) return -1; + return 0; +} + +static void colorband_pos_cb(bContext *C, void *coba_v, void *unused_v) +{ + ColorBand *coba= coba_v; + int a; + + if(coba->tot<2) return; + + for(a=0; a<coba->tot; a++) coba->data[a].cur= a; + qsort(coba->data, coba->tot, sizeof(CBData), vergcband); + for(a=0; a<coba->tot; a++) { + if(coba->data[a].cur==coba->cur) { + // XXX if(coba->cur!=a) addqueue(curarea->win, REDRAW, 0); /* button cur */ + coba->cur= a; + break; + } + } +} + +static void colorband_add_cb(bContext *C, void *coba_v, void *unused_v) +{ + ColorBand *coba= coba_v; + + if(coba->tot < MAXCOLORBAND-1) coba->tot++; + coba->cur= coba->tot-1; + + colorband_pos_cb(C, coba, NULL); + ED_undo_push(C, "Add colorband"); +} + +static void colorband_del_cb(bContext *C, void *coba_v, void *unused_v) +{ + ColorBand *coba= coba_v; + int a; + + if(coba->tot<2) return; + + for(a=coba->cur; a<coba->tot; a++) { + coba->data[a]= coba->data[a+1]; + } + if(coba->cur) coba->cur--; + coba->tot--; + + ED_undo_push(C, "Delete colorband"); + // XXX BIF_preview_changed(ID_TE); +} + + +/* offset aligns from bottom, standard width 300, height 115 */ +static void colorband_buttons_large(uiBlock *block, ColorBand *coba, int xoffs, int yoffs, int redraw) +{ + CBData *cbd; + uiBut *bt; + + if(coba==NULL) return; + + bt= uiDefBut(block, BUT, redraw, "Add", 80+xoffs,95+yoffs,37,20, 0, 0, 0, 0, 0, "Adds a new color position to the colorband"); + uiButSetFunc(bt, colorband_add_cb, coba, NULL); + uiDefButS(block, NUM, redraw, "Cur:", 117+xoffs,95+yoffs,81,20, &coba->cur, 0.0, (float)(coba->tot-1), 0, 0, "Displays the active color from the colorband"); + bt= uiDefBut(block, BUT, redraw, "Del", 199+xoffs,95+yoffs,37,20, 0, 0, 0, 0, 0, "Deletes the active position"); + uiButSetFunc(bt, colorband_del_cb, coba, NULL); + + uiDefButS(block, MENU, redraw, "Interpolation %t|Ease %x1|Cardinal %x3|Linear %x0|B-Spline %x2|Constant %x4", + 236+xoffs, 95+yoffs, 64, 20, &coba->ipotype, 0.0, 0.0, 0, 0, "Sets interpolation type"); + + uiDefBut(block, BUT_COLORBAND, redraw, "", xoffs,65+yoffs,300,30, coba, 0, 0, 0, 0, ""); + + cbd= coba->data + coba->cur; + + uiBlockBeginAlign(block); + bt= uiDefButF(block, NUM, redraw, "Pos", xoffs,40+yoffs,110,20, &cbd->pos, 0.0, 1.0, 10, 0, "Sets the position of the active color"); + uiButSetFunc(bt, colorband_pos_cb, coba, NULL); + uiDefButF(block, COL, redraw, "", xoffs,20+yoffs,110,20, &(cbd->r), 0, 0, 0, B_BANDCOL, ""); + uiDefButF(block, NUMSLI, redraw, "A ", xoffs,yoffs,110,20, &cbd->a, 0.0, 1.0, 10, 0, "Sets the alpha value for this position"); + + uiBlockBeginAlign(block); + uiDefButF(block, NUMSLI, redraw, "R ", 115+xoffs,40+yoffs,185,20, &cbd->r, 0.0, 1.0, B_BANDCOL, 0, "Sets the red value for the active color"); + uiDefButF(block, NUMSLI, redraw, "G ", 115+xoffs,20+yoffs,185,20, &cbd->g, 0.0, 1.0, B_BANDCOL, 0, "Sets the green value for the active color"); + uiDefButF(block, NUMSLI, redraw, "B ", 115+xoffs,yoffs,185,20, &cbd->b, 0.0, 1.0, B_BANDCOL, 0, "Sets the blue value for the active color"); + uiBlockEndAlign(block); +} + +static void colorband_buttons_small(uiBlock *block, ColorBand *coba, rctf *butr, int event) +{ + CBData *cbd; + uiBut *bt; + float unit= (butr->xmax-butr->xmin)/14.0f; + float xs= butr->xmin; + + cbd= coba->data + coba->cur; + + uiBlockBeginAlign(block); + uiDefButF(block, COL, event, "", xs,butr->ymin+20.0f,2.0f*unit,20, &(cbd->r), 0, 0, 0, B_BANDCOL, ""); + uiDefButF(block, NUM, event, "A:", xs+2.0f*unit,butr->ymin+20.0f,4.0f*unit,20, &(cbd->a), 0.0f, 1.0f, 10, 2, ""); + bt= uiDefBut(block, BUT, event, "Add", xs+6.0f*unit,butr->ymin+20.0f,2.0f*unit,20, NULL, 0, 0, 0, 0, "Adds a new color position to the colorband"); + uiButSetFunc(bt, colorband_add_cb, coba, NULL); + bt= uiDefBut(block, BUT, event, "Del", xs+8.0f*unit,butr->ymin+20.0f,2.0f*unit,20, NULL, 0, 0, 0, 0, "Deletes the active position"); + uiButSetFunc(bt, colorband_del_cb, coba, NULL); + + uiDefButS(block, MENU, event, "Interpolation %t|Ease %x1|Cardinal %x3|Linear %x0|B-Spline %x2|Constant %x4", + xs+10.0f*unit, butr->ymin+20.0f, unit*4, 20, &coba->ipotype, 0.0, 0.0, 0, 0, "Sets interpolation type"); + + uiDefBut(block, BUT_COLORBAND, event, "", xs,butr->ymin,butr->xmax-butr->xmin,20.0f, coba, 0, 0, 0, 0, ""); + uiBlockEndAlign(block); + +} + +void colorband_buttons(uiBlock *block, ColorBand *coba, rctf *butr, int small) +{ + if(small) + colorband_buttons_small(block, coba, butr, 0); + else + colorband_buttons_large(block, coba, 0, 0, 0); +} + diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c new file mode 100644 index 00000000000..03487e2301c --- /dev/null +++ b/source/blender/editors/interface/interface_widgets.c @@ -0,0 +1,2036 @@ +/** +* ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2009 Blender Foundation. + * All rights reserved. + * + * Contributor(s): Blender Foundation + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include <limits.h> +#include <math.h> +#include <stdlib.h> +#include <string.h> + +#include "MEM_guardedalloc.h" + +#include "DNA_ID.h" +#include "DNA_screen_types.h" +#include "DNA_userdef_types.h" +#include "DNA_windowmanager_types.h" + +#include "BLI_arithb.h" +#include "BLI_listbase.h" +#include "BLI_rect.h" + +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_utildefines.h" + +#include "BIF_gl.h" +#include "BIF_glutil.h" + +#include "BLF_api.h" + +#include "UI_interface.h" +#include "UI_interface_icons.h" +#include "UI_resources.h" +#include "UI_view2d.h" + +#include "ED_util.h" +#include "ED_types.h" + +#include "interface_intern.h" + +/* ************** widget base functions ************** */ +/* + - in: roundbox codes for corner types and radius + - return: array of [size][2][x,y] points, the edges of the roundbox, + UV coords + + - draw black box with alpha 0 on exact button boundbox + - for ever AA step: + - draw the inner part for a round filled box, with color blend codes or texture coords + - draw outline in outline color + - draw outer part, bottom half, extruded 1 pixel to bottom, for emboss shadow + - draw extra decorations + - draw background color box with alpha 1 on exact button boundbox + + */ + +/* fill this struct with polygon info to draw AA'ed */ +/* it has outline, back, and two optional tria meshes */ + +typedef struct uiWidgetTrias { + int tot; + + float vec[32][2]; + int (*index)[3]; + +} uiWidgetTrias; + +typedef struct uiWidgetStateColors { + char inner_anim[4]; + char inner_anim_sel[4]; + char inner_key[4]; + char inner_key_sel[4]; + char inner_driven[4]; + char inner_driven_sel[4]; +} uiWidgetStateColors; + +typedef struct uiWidgetBase { + + int totvert, halfwayvert; + float outer_v[64][2]; + float inner_v[64][2]; + float inner_uv[64][2]; + + short inner, outline, emboss; /* set on/off */ + + uiWidgetTrias tria1; + uiWidgetTrias tria2; + +} uiWidgetBase; + +/* uiWidgetType: for time being only for visual appearance, + later, a handling callback can be added too +*/ +typedef struct uiWidgetType { + + /* pointer to theme color definition */ + uiWidgetColors *wcol_theme; + + /* converted colors for state */ + uiWidgetColors wcol; + + void (*state)(struct uiWidgetType *, int state); + void (*draw)(uiWidgetColors *, rcti *, int state, int roundboxalign); + void (*custom)(uiBut *, uiWidgetColors *, rcti *, int state, int roundboxalign); + void (*text)(uiFontStyle *, uiWidgetColors *, uiBut *, rcti *); + +} uiWidgetType; + + +/* *********************** draw data ************************** */ + +static float cornervec[9][2]= {{0.0, 0.0}, {0.195, 0.02}, {0.383, 0.067}, {0.55, 0.169}, +{0.707, 0.293}, {0.831, 0.45}, {0.924, 0.617}, {0.98, 0.805}, {1.0, 1.0}}; + +static float jit[8][2]= {{0.468813 , -0.481430}, {-0.155755 , -0.352820}, +{0.219306 , -0.238501}, {-0.393286 , -0.110949}, {-0.024699 , 0.013908}, +{0.343805 , 0.147431}, {-0.272855 , 0.269918}, {0.095909 , 0.388710}}; + +static float num_tria_vert[19][2]= { +{0.382684, 0.923879}, {0.000001, 1.000000}, {-0.382683, 0.923880}, {-0.707107, 0.707107}, +{-0.923879, 0.382684}, {-1.000000, 0.000000}, {-0.923880, -0.382684}, {-0.707107, -0.707107}, +{-0.382683, -0.923880}, {0.000000, -1.000000}, {0.382684, -0.923880}, {0.707107, -0.707107}, +{0.923880, -0.382684}, {1.000000, -0.000000}, {0.923880, 0.382683}, {0.707107, 0.707107}, +{-0.352077, 0.532607}, {-0.352077, -0.549313}, {0.729843, -0.008353}}; + +static int num_tria_face[19][3]= { +{13, 14, 18}, {17, 5, 6}, {12, 13, 18}, {17, 6, 7}, {15, 18, 14}, {16, 4, 5}, {16, 5, 17}, {18, 11, 12}, +{18, 17, 10}, {18, 10, 11}, {17, 9, 10}, {15, 0, 18}, {18, 0, 16}, {3, 4, 16}, {8, 9, 17}, {8, 17, 7}, +{2, 3, 16}, {1, 2, 16}, {16, 0, 1}}; + +static float menu_tria_vert[6][2]= { +{-0.41, 0.16}, {0.41, 0.16}, {0, 0.82}, +{0, -0.82}, {-0.41, -0.16}, {0.41, -0.16}}; + +static int menu_tria_face[2][3]= {{2, 0, 1}, {3, 5, 4}}; + +static float check_tria_vert[6][2]= { +{-0.578579, 0.253369}, {-0.392773, 0.412794}, {-0.004241, -0.328551}, +{-0.003001, 0.034320}, {1.055313, 0.864744}, {0.866408, 1.026895}}; + +static int check_tria_face[4][3]= { +{3, 2, 4}, {3, 4, 5}, {1, 0, 3}, {0, 2, 3}}; + +/* ************************************************* */ + +void ui_draw_anti_tria(float x1, float y1, float x2, float y2, float x3, float y3) +{ + float color[4]; + int j; + + glEnable(GL_BLEND); + glGetFloatv(GL_CURRENT_COLOR, color); + color[3]= 0.125; + glColor4fv(color); + + /* for each AA step */ + for(j=0; j<8; j++) { + glTranslatef(1.0*jit[j][0], 1.0*jit[j][1], 0.0f); + + glBegin(GL_POLYGON); + glVertex2f(x1, y1); + glVertex2f(x2, y2); + glVertex2f(x3, y3); + glEnd(); + + glTranslatef(-1.0*jit[j][0], -1.0*jit[j][1], 0.0f); + } + + glDisable(GL_BLEND); + +} + +static void widget_init(uiWidgetBase *wtb) +{ + wtb->totvert= wtb->halfwayvert= 0; + wtb->tria1.tot= 0; + wtb->tria2.tot= 0; + + wtb->inner= 1; + wtb->outline= 1; + wtb->emboss= 1; +} + +/* helper call, makes shadow rect, with 'sun' above menu, so only shadow to left/right/bottom */ +/* return tot */ +static int round_box_shadow_edges(float (*vert)[2], rcti *rect, float rad, int roundboxalign, float step) +{ + float vec[9][2]; + float minx, miny, maxx, maxy; + int a, tot= 0; + + rad+= step; + + if(2.0f*rad > rect->ymax-rect->ymin) + rad= 0.5f*(rect->ymax-rect->ymin); + + minx= rect->xmin-step; + miny= rect->ymin-step; + maxx= rect->xmax+step; + maxy= rect->ymax+step; + + /* mult */ + for(a=0; a<9; a++) { + vec[a][0]= rad*cornervec[a][0]; + vec[a][1]= rad*cornervec[a][1]; + } + + /* start with left-top, anti clockwise */ + if(roundboxalign & 1) { + for(a=0; a<9; a++, tot++) { + vert[tot][0]= minx+rad-vec[a][0]; + vert[tot][1]= maxy-vec[a][1]; + } + } + else { + for(a=0; a<9; a++, tot++) { + vert[tot][0]= minx; + vert[tot][1]= maxy; + } + } + + if(roundboxalign & 8) { + for(a=0; a<9; a++, tot++) { + vert[tot][0]= minx+vec[a][1]; + vert[tot][1]= miny+rad-vec[a][0]; + } + } + else { + for(a=0; a<9; a++, tot++) { + vert[tot][0]= minx; + vert[tot][1]= miny; + } + } + + if(roundboxalign & 4) { + for(a=0; a<9; a++, tot++) { + vert[tot][0]= maxx-rad+vec[a][0]; + vert[tot][1]= miny+vec[a][1]; + } + } + else { + for(a=0; a<9; a++, tot++) { + vert[tot][0]= maxx; + vert[tot][1]= miny; + } + } + + if(roundboxalign & 2) { + for(a=0; a<9; a++, tot++) { + vert[tot][0]= maxx-vec[a][1]; + vert[tot][1]= maxy-rad+vec[a][0]; + } + } + else { + for(a=0; a<9; a++, tot++) { + vert[tot][0]= maxx; + vert[tot][1]= maxy; + } + } + return tot; +} + +/* this call has 1 extra arg to allow mask outline */ +static void round_box__edges(uiWidgetBase *wt, int roundboxalign, rcti *rect, float rad, float radi) +{ + float vec[9][2], veci[9][2]; + float minx= rect->xmin, miny= rect->ymin, maxx= rect->xmax, maxy= rect->ymax; + float minxi= minx + 1.0f; /* boundbox inner */ + float maxxi= maxx - 1.0f; + float minyi= miny + 1.0f; + float maxyi= maxy - 1.0f; + float facxi= 1.0f/(maxxi-minxi); /* for uv */ + float facyi= 1.0f/(maxyi-minyi); + int a, tot= 0; + + if(2.0f*rad > rect->ymax-rect->ymin) + rad= 0.5f*(rect->ymax-rect->ymin); + + if(2.0f*(radi+1.0f) > rect->ymax-rect->ymin) + radi= 0.5f*(rect->ymax-rect->ymin) - 1.0f; + + /* mult */ + for(a=0; a<9; a++) { + veci[a][0]= radi*cornervec[a][0]; + veci[a][1]= radi*cornervec[a][1]; + vec[a][0]= rad*cornervec[a][0]; + vec[a][1]= rad*cornervec[a][1]; + } + + /* corner left-bottom */ + if(roundboxalign & 8) { + + for(a=0; a<9; a++, tot++) { + wt->inner_v[tot][0]= minxi+veci[a][1]; + wt->inner_v[tot][1]= minyi+radi-veci[a][0]; + + wt->outer_v[tot][0]= minx+vec[a][1]; + wt->outer_v[tot][1]= miny+rad-vec[a][0]; + + wt->inner_uv[tot][0]= facxi*(wt->inner_v[tot][0] - minxi); + wt->inner_uv[tot][1]= facyi*(wt->inner_v[tot][1] - minyi); + } + } + else { + wt->inner_v[tot][0]= minxi; + wt->inner_v[tot][1]= minyi; + + wt->outer_v[tot][0]= minx; + wt->outer_v[tot][1]= miny; + + wt->inner_uv[tot][0]= 0.0f; + wt->inner_uv[tot][1]= 0.0f; + + tot++; + } + + /* corner right-bottom */ + if(roundboxalign & 4) { + + for(a=0; a<9; a++, tot++) { + wt->inner_v[tot][0]= maxxi-radi+veci[a][0]; + wt->inner_v[tot][1]= minyi+veci[a][1]; + + wt->outer_v[tot][0]= maxx-rad+vec[a][0]; + wt->outer_v[tot][1]= miny+vec[a][1]; + + wt->inner_uv[tot][0]= facxi*(wt->inner_v[tot][0] - minxi); + wt->inner_uv[tot][1]= facyi*(wt->inner_v[tot][1] - minyi); + } + } + else { + wt->inner_v[tot][0]= maxxi; + wt->inner_v[tot][1]= minyi; + + wt->outer_v[tot][0]= maxx; + wt->outer_v[tot][1]= miny; + + wt->inner_uv[tot][0]= 1.0f; + wt->inner_uv[tot][1]= 0.0f; + + tot++; + } + + wt->halfwayvert= tot; + + /* corner right-top */ + if(roundboxalign & 2) { + + for(a=0; a<9; a++, tot++) { + wt->inner_v[tot][0]= maxxi-veci[a][1]; + wt->inner_v[tot][1]= maxyi-radi+veci[a][0]; + + wt->outer_v[tot][0]= maxx-vec[a][1]; + wt->outer_v[tot][1]= maxy-rad+vec[a][0]; + + wt->inner_uv[tot][0]= facxi*(wt->inner_v[tot][0] - minxi); + wt->inner_uv[tot][1]= facyi*(wt->inner_v[tot][1] - minyi); + } + } + else { + wt->inner_v[tot][0]= maxxi; + wt->inner_v[tot][1]= maxyi; + + wt->outer_v[tot][0]= maxx; + wt->outer_v[tot][1]= maxy; + + wt->inner_uv[tot][0]= 1.0f; + wt->inner_uv[tot][1]= 1.0f; + + tot++; + } + + /* corner left-top */ + if(roundboxalign & 1) { + + for(a=0; a<9; a++, tot++) { + wt->inner_v[tot][0]= minxi+radi-veci[a][0]; + wt->inner_v[tot][1]= maxyi-veci[a][1]; + + wt->outer_v[tot][0]= minx+rad-vec[a][0]; + wt->outer_v[tot][1]= maxy-vec[a][1]; + + wt->inner_uv[tot][0]= facxi*(wt->inner_v[tot][0] - minxi); + wt->inner_uv[tot][1]= facyi*(wt->inner_v[tot][1] - minyi); + } + + } + else { + + wt->inner_v[tot][0]= minxi; + wt->inner_v[tot][1]= maxyi; + + wt->outer_v[tot][0]= minx; + wt->outer_v[tot][1]= maxy; + + wt->inner_uv[tot][0]= 0.0f; + wt->inner_uv[tot][1]= 1.0f; + + tot++; + } + + wt->totvert= tot; +} + +static void round_box_edges(uiWidgetBase *wt, int roundboxalign, rcti *rect, float rad) +{ + round_box__edges(wt, roundboxalign, rect, rad, rad-1.0f); +} + + +/* based on button rect, return scaled array of triangles */ +static void widget_num_tria(uiWidgetTrias *tria, rcti *rect, float triasize, char where) +{ + float centx, centy, size; + int a; + + /* center position and size */ + centx= (float)rect->xmin + 0.5f*(rect->ymax-rect->ymin); + centy= (float)rect->ymin + 0.5f*(rect->ymax-rect->ymin); + size= -0.5f*triasize*(rect->ymax-rect->ymin); + + if(where=='r') { + centx= (float)rect->xmax - 0.5f*(rect->ymax-rect->ymin); + size= -size; + } + + for(a=0; a<19; a++) { + tria->vec[a][0]= size*num_tria_vert[a][0] + centx; + tria->vec[a][1]= size*num_tria_vert[a][1] + centy; + } + + tria->tot= 19; + tria->index= num_tria_face; +} + +static void widget_trias_draw(uiWidgetTrias *tria) +{ + int a; + + glBegin(GL_TRIANGLES); + for(a=0; a<tria->tot; a++) { + glVertex2fv(tria->vec[ tria->index[a][0] ]); + glVertex2fv(tria->vec[ tria->index[a][1] ]); + glVertex2fv(tria->vec[ tria->index[a][2] ]); + } + glEnd(); + +} + +static void widget_menu_trias(uiWidgetTrias *tria, rcti *rect) +{ + float centx, centy, size, asp; + int a; + + /* center position and size */ + centx= rect->xmax - 0.5f*(rect->ymax-rect->ymin); + centy= rect->ymin + 0.5f*(rect->ymax-rect->ymin); + size= 0.4f*(rect->ymax-rect->ymin); + + /* XXX exception */ + asp= ((float)rect->xmax-rect->xmin)/((float)rect->ymax-rect->ymin); + if(asp > 1.2f && asp < 2.6f) + centx= rect->xmax - 0.3f*(rect->ymax-rect->ymin); + + for(a=0; a<6; a++) { + tria->vec[a][0]= size*menu_tria_vert[a][0] + centx; + tria->vec[a][1]= size*menu_tria_vert[a][1] + centy; + } + + tria->tot= 2; + tria->index= menu_tria_face; +} + +static void widget_check_trias(uiWidgetTrias *tria, rcti *rect) +{ + float centx, centy, size; + int a; + + /* center position and size */ + centx= rect->xmin + 0.5f*(rect->ymax-rect->ymin); + centy= rect->ymin + 0.5f*(rect->ymax-rect->ymin); + size= 0.5f*(rect->ymax-rect->ymin); + + for(a=0; a<6; a++) { + tria->vec[a][0]= size*check_tria_vert[a][0] + centx; + tria->vec[a][1]= size*check_tria_vert[a][1] + centy; + } + + tria->tot= 4; + tria->index= check_tria_face; +} + + +/* prepares shade colors */ +static void shadecolors4(char *coltop, char *coldown, char *color, short shadetop, short shadedown) +{ + + coltop[0]= CLAMPIS(color[0]+shadetop, 0, 255); + coltop[1]= CLAMPIS(color[1]+shadetop, 0, 255); + coltop[2]= CLAMPIS(color[2]+shadetop, 0, 255); + coltop[3]= color[3]; + + coldown[0]= CLAMPIS(color[0]+shadedown, 0, 255); + coldown[1]= CLAMPIS(color[1]+shadedown, 0, 255); + coldown[2]= CLAMPIS(color[2]+shadedown, 0, 255); + coldown[3]= color[3]; +} + +static void round_box_shade_col4(char *col1, char *col2, float fac) +{ + int faci, facm; + char col[4]; + + faci= floor(255.1f*fac); + facm= 255-faci; + + col[0]= (faci*col1[0] + facm*col2[0])>>8; + col[1]= (faci*col1[1] + facm*col2[1])>>8; + col[2]= (faci*col1[2] + facm*col2[2])>>8; + col[3]= (faci*col1[3] + facm*col2[3])>>8; + + glColor4ubv(col); +} + +static void widgetbase_outline(uiWidgetBase *wtb) +{ + int a; + + /* outline */ + glBegin(GL_QUAD_STRIP); + for(a=0; a<wtb->totvert; a++) { + glVertex2fv(wtb->outer_v[a]); + glVertex2fv(wtb->inner_v[a]); + } + glVertex2fv(wtb->outer_v[0]); + glVertex2fv(wtb->inner_v[0]); + glEnd(); +} + +static void widgetbase_draw(uiWidgetBase *wtb, uiWidgetColors *wcol) +{ + int j, a; + + glEnable(GL_BLEND); + + /* backdrop non AA */ + if(wtb->inner) { + if(wcol->shaded==0) { + + /* filled center, solid */ + glColor4ubv(wcol->inner); + glBegin(GL_POLYGON); + for(a=0; a<wtb->totvert; a++) + glVertex2fv(wtb->inner_v[a]); + glEnd(); + + } + else { + char col1[4], col2[4]; + + shadecolors4(col1, col2, wcol->inner, wcol->shadetop, wcol->shadedown); + + glShadeModel(GL_SMOOTH); + glBegin(GL_POLYGON); + for(a=0; a<wtb->totvert; a++) { + round_box_shade_col4(col1, col2, wtb->inner_uv[a][1]); + glVertex2fv(wtb->inner_v[a]); + } + glEnd(); + glShadeModel(GL_FLAT); + } + } + + /* for each AA step */ + if(wtb->outline) { + for(j=0; j<8; j++) { + glTranslatef(1.0*jit[j][0], 1.0*jit[j][1], 0.0f); + + /* outline */ + glColor4ub(wcol->outline[0], wcol->outline[1], wcol->outline[2], 32); + glBegin(GL_QUAD_STRIP); + for(a=0; a<wtb->totvert; a++) { + glVertex2fv(wtb->outer_v[a]); + glVertex2fv(wtb->inner_v[a]); + } + glVertex2fv(wtb->outer_v[0]); + glVertex2fv(wtb->inner_v[0]); + glEnd(); + + /* emboss bottom shadow */ + if(wtb->emboss) { + glColor4f(1.0f, 1.0f, 1.0f, 0.02f); + glBegin(GL_QUAD_STRIP); + for(a=0; a<wtb->halfwayvert; a++) { + glVertex2fv(wtb->outer_v[a]); + glVertex2f(wtb->outer_v[a][0], wtb->outer_v[a][1]-1.0f); + } + glEnd(); + } + + glTranslatef(-1.0*jit[j][0], -1.0*jit[j][1], 0.0f); + } + } + + /* decoration */ + if(wtb->tria1.tot || wtb->tria2.tot) { + /* for each AA step */ + for(j=0; j<8; j++) { + glTranslatef(1.0*jit[j][0], 1.0*jit[j][1], 0.0f); + + if(wtb->tria1.tot) { + glColor4ub(wcol->item[0], wcol->item[1], wcol->item[2], 32); + widget_trias_draw(&wtb->tria1); + } + if(wtb->tria2.tot) { + glColor4ub(wcol->item[0], wcol->item[1], wcol->item[2], 32); + widget_trias_draw(&wtb->tria2); + } + + glTranslatef(-1.0*jit[j][0], -1.0*jit[j][1], 0.0f); + } + } + + glDisable(GL_BLEND); + +} + +/* *********************** text/icon ************************************** */ + + +/* icons have been standardized... and this call draws in untransformed coordinates */ +#define ICON_HEIGHT 16.0f + +static void widget_draw_icon(uiBut *but, BIFIconID icon, int blend, rcti *rect) +{ + int xs=0, ys=0; + float aspect, height; + + /* this icon doesn't need draw... */ + if(icon==ICON_BLANK1 && (but->flag & UI_ICON_SUBMENU)==0) return; + + /* we need aspect from block, for menus... these buttons are scaled in uiPositionBlock() */ + aspect= but->block->aspect; + if(aspect != but->aspect) { + /* prevent scaling up icon in pupmenu */ + if (aspect < 1.0f) { + height= ICON_HEIGHT; + aspect = 1.0f; + + } + else + height= ICON_HEIGHT/aspect; + } + else + height= ICON_HEIGHT; + + /* calculate blend color */ + if ELEM3(but->type, TOG, ROW, TOGN) { + if(but->flag & UI_SELECT); + else if(but->flag & UI_ACTIVE); + else blend= -60; + } + + glEnable(GL_BLEND); + + if(icon && icon!=ICON_BLANK1) { + if(but->flag & UI_ICON_LEFT) { + if (but->type==BUT_TOGDUAL) { + if (but->drawstr[0]) { + xs= rect->xmin-1; + } else { + xs= (rect->xmin+rect->xmax- height)/2; + } + } + else if (but->block->flag & UI_BLOCK_LOOP) { + if(but->type==SEARCH_MENU) + xs= rect->xmin+4; + else + xs= rect->xmin+1; + } + else if ((but->type==ICONROW) || (but->type==ICONTEXTROW)) { + xs= rect->xmin+3; + } + else { + xs= rect->xmin+4; + } + ys= (rect->ymin+rect->ymax- height)/2; + } + else { + xs= (rect->xmin+rect->xmax- height)/2; + ys= (rect->ymin+rect->ymax- height)/2; + } + + UI_icon_draw_aspect_blended(xs, ys, icon, aspect, blend); + } + + if(but->flag & UI_ICON_SUBMENU) { + xs= rect->xmax-17; + ys= (rect->ymin+rect->ymax- height)/2; + + UI_icon_draw_aspect_blended(xs, ys, ICON_RIGHTARROW_THIN, aspect, blend); + } + + glDisable(GL_BLEND); +} + +/* sets but->ofs to make sure text is correctly visible */ +static void ui_text_leftclip(uiFontStyle *fstyle, uiBut *but, rcti *rect) +{ + int okwidth= rect->xmax-rect->xmin; + + /* need to set this first */ + uiStyleFontSet(fstyle); + + but->strwidth= BLF_width(but->drawstr); + but->ofs= 0; + + while(but->strwidth > okwidth ) { + + but->ofs++; + but->strwidth= BLF_width(but->drawstr+but->ofs); + + /* textbut exception */ + if(but->editstr && but->pos != -1) { + int pos= but->pos+strlen(but->str); + + if(pos-1 < but->ofs) { + pos= but->ofs-pos+1; + but->ofs -= pos; + if(but->ofs<0) { + but->ofs= 0; + pos--; + } + but->drawstr[ strlen(but->drawstr)-pos ]= 0; + } + } + + if(but->strwidth < 10) break; + } +} + +static void widget_draw_text(uiFontStyle *fstyle, uiWidgetColors *wcol, uiBut *but, rcti *rect) +{ +// int transopts; + char *cpoin = NULL; + + uiStyleFontSet(fstyle); + + if(but->editstr || (but->flag & UI_TEXT_LEFT)) + fstyle->align= UI_STYLE_TEXT_LEFT; + else + fstyle->align= UI_STYLE_TEXT_CENTER; + + /* text button selection and cursor */ + if(but->editstr && but->pos != -1) { + short t, pos, ch; + short selsta_tmp, selend_tmp, selsta_draw, selwidth_draw; + + if ((but->selend - but->selsta) > 0) { + /* XXX weak, why is this? (ton) */ + t= but->str[0]?1:-2; + + /* text button selection */ + selsta_tmp = but->selsta + strlen(but->str); + selend_tmp = but->selend + strlen(but->str); + + if(but->drawstr[0]!=0) { + ch= but->drawstr[selsta_tmp]; + but->drawstr[selsta_tmp]= 0; + + selsta_draw = BLF_width(but->drawstr+but->ofs) + t; + + but->drawstr[selsta_tmp]= ch; + + ch= but->drawstr[selend_tmp]; + but->drawstr[selend_tmp]= 0; + + selwidth_draw = BLF_width(but->drawstr+but->ofs) + t; + + but->drawstr[selend_tmp]= ch; + + glColor3ubv(wcol->item); + glRects(rect->xmin+selsta_draw+1, rect->ymin+2, rect->xmin+selwidth_draw+1, rect->ymax-2); + } + } else { + /* text cursor */ + pos= but->pos+strlen(but->str); + if(pos >= but->ofs) { + if(but->drawstr[0]!=0) { + ch= but->drawstr[pos]; + but->drawstr[pos]= 0; + + t= BLF_width(but->drawstr+but->ofs) + 1; + + but->drawstr[pos]= ch; + } + else t= 1; + + glColor3ub(255,0,0); + glRects(rect->xmin+t, rect->ymin+2, rect->xmin+t+2, rect->ymax-2); + } + } + } + // ui_rasterpos_safe(x, y, but->aspect); +// if(but->type==IDPOIN) transopts= 0; // no translation, of course! +// else transopts= ui_translate_buttons(); + + /* cut string in 2 parts - only for menu entries */ + if(ELEM5(but->type, SLI, NUM, TEX, NUMSLI, NUMABS)==0) { + cpoin= strchr(but->drawstr, '|'); + if(cpoin) *cpoin= 0; + } + + glColor3ubv(wcol->text); + uiStyleFontDraw(fstyle, rect, but->drawstr+but->ofs); + + /* part text right aligned */ + if(cpoin) { + fstyle->align= UI_STYLE_TEXT_RIGHT; + rect->xmax-=5; + uiStyleFontDraw(fstyle, rect, cpoin+1); + *cpoin= '|'; + } +} + +/* draws text and icons for buttons */ +static void widget_draw_text_icon(uiFontStyle *fstyle, uiWidgetColors *wcol, uiBut *but, rcti *rect) +{ + + if(but==NULL) return; + + /* cutting off from left part */ + if ELEM3(but->type, NUM, NUMABS, TEX) { + ui_text_leftclip(fstyle, but, rect); + } + else but->ofs= 0; + + /* check for button text label */ + if (but->type == ICONTEXTROW) { + widget_draw_icon(but, (BIFIconID) (but->icon+but->iconadd), 0, rect); + } + else { + + if(but->type==BUT_TOGDUAL) { + int dualset= 0; + if(but->pointype==SHO) + dualset= BTST( *(((short *)but->poin)+1), but->bitnr); + else if(but->pointype==INT) + dualset= BTST( *(((int *)but->poin)+1), but->bitnr); + + widget_draw_icon(but, ICON_DOT, dualset?0:-100, rect); + } + + /* If there's an icon too (made with uiDefIconTextBut) then draw the icon + and offset the text label to accomodate it */ + + if (but->flag & UI_HAS_ICON) { + widget_draw_icon(but, but->icon, 0, rect); + + rect->xmin += UI_icon_get_width(but->icon); + + if(but->editstr || (but->flag & UI_TEXT_LEFT)) + rect->xmin += 5; + } + else if(but->flag & UI_TEXT_LEFT) + rect->xmin += 5; + + /* always draw text for textbutton cursor */ + widget_draw_text(fstyle, wcol, but, rect); + + } +} + + + +/* *********************** widget types ************************************* */ + + +/* uiWidgetStateColors + char inner_anim[4]; + char inner_anim_sel[4]; + char inner_key[4]; + char inner_key_sel[4]; + char inner_driven[4]; + char inner_driven_sel[4]; + +*/ + +static struct uiWidgetStateColors wcol_state= { + {115, 190, 76, 255}, + {90, 166, 51, 255}, + {240, 235, 100, 255}, + {148, 204, 76, 255}, + {180, 0, 255, 255}, + {153, 0, 230, 255} +}; + +/* uiWidgetColors + float outline[3]; + float inner[4]; + float inner_sel[4]; + float item[3]; + float text[3]; + float text_sel[3]; + + short shaded; + float shadetop, shadedown; +*/ + +static struct uiWidgetColors wcol_num= { + {25, 25, 25, 255}, + {180, 180, 180, 255}, + {153, 153, 153, 255}, + {90, 90, 90, 255}, + + {0, 0, 0, 255}, + {255, 255, 255, 255}, + + 1, + -20, 0 +}; + +static struct uiWidgetColors wcol_numslider= { + {25, 25, 25, 255}, + {180, 180, 180, 255}, + {153, 153, 153, 255}, + {128, 128, 128, 255}, + + {0, 0, 0, 255}, + {255, 255, 255, 255}, + + 1, + -20, 0 +}; + +static struct uiWidgetColors wcol_text= { + {25, 25, 25, 255}, + {153, 153, 153, 255}, + {153, 153, 153, 255}, + {90, 90, 90, 255}, + + {0, 0, 0, 255}, + {255, 255, 255, 255}, + + 1, + 0, 25 +}; + +static struct uiWidgetColors wcol_option= { + {0, 0, 0, 255}, + {70, 70, 70, 255}, + {70, 70, 70, 255}, + {255, 255, 255, 255}, + + {0, 0, 0, 255}, + {255, 255, 255, 255}, + + 1, + 15, -15 +}; + +/* button that shows popup */ +static struct uiWidgetColors wcol_menu= { + {0, 0, 0, 255}, + {70, 70, 70, 255}, + {70, 70, 70, 255}, + {255, 255, 255, 255}, + + {255, 255, 255, 255}, + {204, 204, 204, 255}, + + 1, + 15, -15 +}; + +/* button that starts pulldown */ +static struct uiWidgetColors wcol_pulldown= { + {0, 0, 0, 255}, + {63, 63, 63, 255}, + {86, 128, 194, 255}, + {255, 255, 255, 255}, + + {0, 0, 0, 255}, + {0, 0, 0, 255}, + + 0, + 25, -20 +}; + +/* button inside menu */ +static struct uiWidgetColors wcol_menu_item= { + {0, 0, 0, 255}, + {0, 0, 0, 0}, + {86, 128, 194, 255}, + {255, 255, 255, 255}, + + {255, 255, 255, 255}, + {0, 0, 0, 255}, + + 0, + 38, 0 +}; + +/* backdrop menu + title text color */ +static struct uiWidgetColors wcol_menu_back= { + {0, 0, 0, 255}, + {25, 25, 25, 230}, + {46, 124, 217, 204}, + {255, 255, 255, 255}, + + {255, 255, 255, 255}, + {0, 0, 0, 255}, + + 0, + 25, -20 +}; + + +static struct uiWidgetColors wcol_radio= { + {0, 0, 0, 255}, + {70, 70, 70, 255}, + {86, 128, 194, 255}, + {255, 255, 255, 255}, + + {255, 255, 255, 255}, + {0, 0, 0, 255}, + + 1, + 15, -15 +}; + +static struct uiWidgetColors wcol_regular= { + {25, 25, 25, 255}, + {153, 153, 153, 255}, + {100, 100, 100, 255}, + {25, 25, 25, 255}, + + {0, 0, 0, 255}, + {255, 255, 255, 255}, + + 0, + 0, 0 +}; + +static struct uiWidgetColors wcol_tool= { + {25, 25, 25, 255}, + {153, 153, 153, 255}, + {100, 100, 100, 255}, + {25, 25, 25, 255}, + + {0, 0, 0, 255}, + {255, 255, 255, 255}, + + 1, + 25, -25 +}; + +static struct uiWidgetColors wcol_box= { + {25, 25, 25, 255}, + {128, 128, 128, 255}, + {100, 100, 100, 255}, + {25, 25, 25, 255}, + + {0, 0, 0, 255}, + {255, 255, 255, 255}, + + 0, + 0, 0 +}; + + +/* called for theme init (new theme) and versions */ +void ui_widget_color_init(ThemeUI *tui) +{ + + tui->wcol_regular= wcol_regular; + tui->wcol_tool= wcol_tool; + tui->wcol_radio= wcol_radio; + tui->wcol_text= wcol_text; + tui->wcol_option= wcol_option; + tui->wcol_num= wcol_num; + tui->wcol_numslider= wcol_numslider; + tui->wcol_menu= wcol_menu; + tui->wcol_pulldown= wcol_pulldown; + tui->wcol_menu_back= wcol_menu_back; + tui->wcol_menu_item= wcol_menu_item; + tui->wcol_box= wcol_box; + + tui->iconfile[0]= 0; +} + +/* ************ button callbacks, state ***************** */ + +/* copy colors from theme, and set changes in it based on state */ +static void widget_state(uiWidgetType *wt, int state) +{ + wt->wcol= *(wt->wcol_theme); + + if(state & UI_SELECT) { + if(state & UI_BUT_ANIMATED_KEY) + QUATCOPY(wt->wcol.inner, wcol_state.inner_key_sel) + else if(state & UI_BUT_ANIMATED) + QUATCOPY(wt->wcol.inner, wcol_state.inner_anim_sel) + else if(state & UI_BUT_DRIVEN) + QUATCOPY(wt->wcol.inner, wcol_state.inner_driven_sel) + else + QUATCOPY(wt->wcol.inner, wt->wcol.inner_sel) + + VECCOPY(wt->wcol.text, wt->wcol.text_sel); + + /* only flip shade if it's not "pushed in" already */ + if(wt->wcol.shaded && wt->wcol.shadetop>wt->wcol.shadedown) { + SWAP(short, wt->wcol.shadetop, wt->wcol.shadedown); + } + } + else { + if(state & UI_BUT_ANIMATED_KEY) + QUATCOPY(wt->wcol.inner, wcol_state.inner_key) + else if(state & UI_BUT_ANIMATED) + QUATCOPY(wt->wcol.inner, wcol_state.inner_anim) + else if(state & UI_BUT_DRIVEN) + QUATCOPY(wt->wcol.inner, wcol_state.inner_driven) + + if(state & UI_ACTIVE) { /* mouse over? */ + wt->wcol.inner[0]= wt->wcol.inner[0]>=240? 255 : wt->wcol.inner[0]+15; + wt->wcol.inner[1]= wt->wcol.inner[1]>=240? 255 : wt->wcol.inner[1]+15; + wt->wcol.inner[2]= wt->wcol.inner[2]>=240? 255 : wt->wcol.inner[2]+15; + } + } +} + +/* labels use theme colors for text */ +static void widget_state_label(uiWidgetType *wt, int state) +{ + /* call this for option button */ + widget_state(wt, state); + + if(state & UI_SELECT) + UI_GetThemeColor4ubv(TH_TEXT_HI, wt->wcol.text); + else + UI_GetThemeColor4ubv(TH_TEXT, wt->wcol.text); + +} + + +/* special case, button that calls pulldown */ +static void widget_state_pulldown(uiWidgetType *wt, int state) +{ + wt->wcol= *(wt->wcol_theme); + + QUATCOPY(wt->wcol.inner, wt->wcol.inner_sel); + VECCOPY(wt->wcol.outline, wt->wcol.inner); + + if(state & UI_ACTIVE) + VECCOPY(wt->wcol.text, wt->wcol.text_sel); +} + +/* special case, menu items */ +static void widget_state_menu_item(uiWidgetType *wt, int state) +{ + wt->wcol= *(wt->wcol_theme); + + if(state & (UI_BUT_DISABLED|UI_BUT_INACTIVE)) { + wt->wcol.text[0]= 0.5f*(wt->wcol.text[0]+wt->wcol.text_sel[0]); + wt->wcol.text[1]= 0.5f*(wt->wcol.text[1]+wt->wcol.text_sel[1]); + wt->wcol.text[2]= 0.5f*(wt->wcol.text[2]+wt->wcol.text_sel[2]); + } + else if(state & UI_ACTIVE) { + QUATCOPY(wt->wcol.inner, wt->wcol.inner_sel); + VECCOPY(wt->wcol.text, wt->wcol.text_sel); + + wt->wcol.shaded= 1; + } +} + + +/* ************ menu backdrop ************************* */ + +/* outside of rect, rad to left/bottom/right */ +static void widget_softshadow(rcti *rect, int roundboxalign, float radin, float radout) +{ + uiWidgetBase wtb; + rcti rect1= *rect; + float alpha, alphastep; + int step, tot, a; + + /* prevent tooltips to not show round shadow */ + if( 2.0f*radout > 0.2f*(rect1.ymax-rect1.ymin) ) + rect1.ymax -= 0.2f*(rect1.ymax-rect1.ymin); + else + rect1.ymax -= 2.0f*radout; + + /* inner part */ + tot= round_box_shadow_edges(wtb.inner_v, &rect1, radin, roundboxalign & 12, 0.0f); + + /* inverse linear shadow alpha */ + alpha= 0.15; + alphastep= 0.67; + + for(step= 1; step<=radout; step++, alpha*=alphastep) { + round_box_shadow_edges(wtb.outer_v, &rect1, radin, 15, (float)step); + + glColor4f(0.0f, 0.0f, 0.0f, alpha); + + glBegin(GL_QUAD_STRIP); + for(a=0; a<tot; a++) { + glVertex2fv(wtb.outer_v[a]); + glVertex2fv(wtb.inner_v[a]); + } + glEnd(); + } + +} + +static void widget_menu_back(uiWidgetColors *wcol, rcti *rect, int flag, int direction) +{ + uiWidgetBase wtb; + int roundboxalign= 15; + + widget_init(&wtb); + + /* menu is 2nd level or deeper */ + if (flag & UI_BLOCK_POPUP) { + //rect->ymin -= 4.0; + //rect->ymax += 4.0; + } + else if (direction == UI_DOWN) { + roundboxalign= 12; + rect->ymin -= 4.0; + } + else if (direction == UI_TOP) { + roundboxalign= 3; + rect->ymax += 4.0; + } + + glEnable(GL_BLEND); + widget_softshadow(rect, roundboxalign, 5.0f, 8.0f); + + round_box_edges(&wtb, roundboxalign, rect, 5.0f); + wtb.emboss= 0; + widgetbase_draw(&wtb, wcol); + + glDisable(GL_BLEND); +} + +/* ************ custom buttons, old stuff ************** */ + +/* draws in resolution of 20x4 colors */ +static void ui_draw_but_HSVCUBE(uiBut *but, rcti *rect) +{ + int a; + float h,s,v; + float dx, dy, sx1, sx2, sy, x, y; + float col0[4][3]; // left half, rect bottom to top + float col1[4][3]; // right half, rect bottom to top + + h= but->hsv[0]; + s= but->hsv[1]; + v= but->hsv[2]; + + /* draw series of gouraud rects */ + glShadeModel(GL_SMOOTH); + + if(but->a1==0) { // H and V vary + hsv_to_rgb(0.0, s, 0.0, &col1[0][0], &col1[0][1], &col1[0][2]); + hsv_to_rgb(0.0, s, 0.333, &col1[1][0], &col1[1][1], &col1[1][2]); + hsv_to_rgb(0.0, s, 0.666, &col1[2][0], &col1[2][1], &col1[2][2]); + hsv_to_rgb(0.0, s, 1.0, &col1[3][0], &col1[3][1], &col1[3][2]); + x= h; y= v; + } + else if(but->a1==1) { // H and S vary + hsv_to_rgb(0.0, 0.0, v, &col1[0][0], &col1[0][1], &col1[0][2]); + hsv_to_rgb(0.0, 0.333, v, &col1[1][0], &col1[1][1], &col1[1][2]); + hsv_to_rgb(0.0, 0.666, v, &col1[2][0], &col1[2][1], &col1[2][2]); + hsv_to_rgb(0.0, 1.0, v, &col1[3][0], &col1[3][1], &col1[3][2]); + x= h; y= s; + } + else if(but->a1==2) { // S and V vary + hsv_to_rgb(h, 0.0, 0.0, &col1[0][0], &col1[0][1], &col1[0][2]); + hsv_to_rgb(h, 0.333, 0.0, &col1[1][0], &col1[1][1], &col1[1][2]); + hsv_to_rgb(h, 0.666, 0.0, &col1[2][0], &col1[2][1], &col1[2][2]); + hsv_to_rgb(h, 1.0, 0.0, &col1[3][0], &col1[3][1], &col1[3][2]); + x= v; y= s; + } + else { // only hue slider + hsv_to_rgb(0.0, 1.0, 1.0, &col1[0][0], &col1[0][1], &col1[0][2]); + VECCOPY(col1[1], col1[0]); + VECCOPY(col1[2], col1[0]); + VECCOPY(col1[3], col1[0]); + x= h; y= 0.5; + } + + for(dx=0.0; dx<1.0; dx+= 0.05) { + // previous color + VECCOPY(col0[0], col1[0]); + VECCOPY(col0[1], col1[1]); + VECCOPY(col0[2], col1[2]); + VECCOPY(col0[3], col1[3]); + + // new color + if(but->a1==0) { // H and V vary + hsv_to_rgb(dx, s, 0.0, &col1[0][0], &col1[0][1], &col1[0][2]); + hsv_to_rgb(dx, s, 0.333, &col1[1][0], &col1[1][1], &col1[1][2]); + hsv_to_rgb(dx, s, 0.666, &col1[2][0], &col1[2][1], &col1[2][2]); + hsv_to_rgb(dx, s, 1.0, &col1[3][0], &col1[3][1], &col1[3][2]); + } + else if(but->a1==1) { // H and S vary + hsv_to_rgb(dx, 0.0, v, &col1[0][0], &col1[0][1], &col1[0][2]); + hsv_to_rgb(dx, 0.333, v, &col1[1][0], &col1[1][1], &col1[1][2]); + hsv_to_rgb(dx, 0.666, v, &col1[2][0], &col1[2][1], &col1[2][2]); + hsv_to_rgb(dx, 1.0, v, &col1[3][0], &col1[3][1], &col1[3][2]); + } + else if(but->a1==2) { // S and V vary + hsv_to_rgb(h, 0.0, dx, &col1[0][0], &col1[0][1], &col1[0][2]); + hsv_to_rgb(h, 0.333, dx, &col1[1][0], &col1[1][1], &col1[1][2]); + hsv_to_rgb(h, 0.666, dx, &col1[2][0], &col1[2][1], &col1[2][2]); + hsv_to_rgb(h, 1.0, dx, &col1[3][0], &col1[3][1], &col1[3][2]); + } + else { // only H + hsv_to_rgb(dx, 1.0, 1.0, &col1[0][0], &col1[0][1], &col1[0][2]); + VECCOPY(col1[1], col1[0]); + VECCOPY(col1[2], col1[0]); + VECCOPY(col1[3], col1[0]); + } + + // rect + sx1= rect->xmin + dx*(rect->xmax-rect->xmin); + sx2= rect->xmin + (dx+0.05)*(rect->xmax-rect->xmin); + sy= rect->ymin; + dy= (rect->ymax-rect->ymin)/3.0; + + glBegin(GL_QUADS); + for(a=0; a<3; a++, sy+=dy) { + glColor3fv(col0[a]); + glVertex2f(sx1, sy); + + glColor3fv(col1[a]); + glVertex2f(sx2, sy); + + glColor3fv(col1[a+1]); + glVertex2f(sx2, sy+dy); + + glColor3fv(col0[a+1]); + glVertex2f(sx1, sy+dy); + } + glEnd(); + } + + glShadeModel(GL_FLAT); + + /* cursor */ + x= rect->xmin + x*(rect->xmax-rect->xmin); + y= rect->ymin + y*(rect->ymax-rect->ymin); + CLAMP(x, rect->xmin+3.0, rect->xmax-3.0); + CLAMP(y, rect->ymin+3.0, rect->ymax-3.0); + + fdrawXORcirc(x, y, 3.1); + + /* outline */ + glColor3ub(0, 0, 0); + fdrawbox((rect->xmin), (rect->ymin), (rect->xmax), (rect->ymax)); +} + + +/* ************ button callbacks, draw ***************** */ + +static void widget_numbut(uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign) +{ + uiWidgetBase wtb; + float rad= 0.5f*(rect->ymax - rect->ymin); + + widget_init(&wtb); + + /* fully rounded */ + round_box_edges(&wtb, roundboxalign, rect, rad); + + /* decoration */ + if(!(state & UI_TEXTINPUT)) { + widget_num_tria(&wtb.tria1, rect, 0.6f, 0); + widget_num_tria(&wtb.tria2, rect, 0.6f, 'r'); + } + widgetbase_draw(&wtb, wcol); + + /* text space */ + rect->xmin += (rect->ymax-rect->ymin); + rect->xmax -= (rect->ymax-rect->ymin); + +} + +static void widget_numslider(uiBut *but, uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign) +{ + uiWidgetBase wtb, wtb1; + rcti rect1; + double value; + float offs, fac; + char outline[3]; + + widget_init(&wtb); + widget_init(&wtb1); + + /* backdrop first */ + + /* fully rounded */ + offs= 0.5f*(rect->ymax - rect->ymin); + round_box_edges(&wtb, roundboxalign, rect, offs); + + wtb.outline= 0; + widgetbase_draw(&wtb, wcol); + + /* slider part */ + VECCOPY(outline, wcol->outline); + VECCOPY(wcol->outline, wcol->item); + VECCOPY(wcol->inner, wcol->item); + SWAP(short, wcol->shadetop, wcol->shadedown); + + rect1= *rect; + + value= ui_get_but_val(but); + fac= (value-but->softmin)*(rect1.xmax - rect1.xmin - offs)/(but->softmax - but->softmin); + + /* left part of slider, always rounded */ + rect1.xmax= rect1.xmin + ceil(offs+1.0f); + round_box_edges(&wtb1, roundboxalign & ~6, &rect1, offs); + wtb1.outline= 0; + widgetbase_draw(&wtb1, wcol); + + /* right part of slider, interpolate roundness */ + rect1.xmax= rect1.xmin + fac + offs; + rect1.xmin+= floor(offs-1.0f); + if(rect1.xmax + offs > rect->xmax) + offs*= (rect1.xmax + offs - rect->xmax)/offs; + else + offs= 0.0f; + round_box_edges(&wtb1, roundboxalign & ~9, &rect1, offs); + + widgetbase_draw(&wtb1, wcol); + VECCOPY(wcol->outline, outline); + SWAP(short, wcol->shadetop, wcol->shadedown); + + /* outline */ + wtb.outline= 1; + wtb.inner= 0; + widgetbase_draw(&wtb, wcol); + + /* text space */ + rect->xmin += (rect->ymax-rect->ymin); + rect->xmax -= (rect->ymax-rect->ymin); + +} + +static void widget_swatch(uiBut *but, uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign) +{ + uiWidgetBase wtb; + float col[4]; + + widget_init(&wtb); + + /* half rounded */ + round_box_edges(&wtb, roundboxalign, rect, 4.0f); + + ui_get_but_vectorf(but, col); + wcol->inner[0]= FTOCHAR(col[0]); + wcol->inner[1]= FTOCHAR(col[1]); + wcol->inner[2]= FTOCHAR(col[2]); + + widgetbase_draw(&wtb, wcol); + +} + + +static void widget_textbut(uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign) +{ + uiWidgetBase wtb; + + widget_init(&wtb); + + /* half rounded */ + round_box_edges(&wtb, roundboxalign, rect, 5.0f); + + widgetbase_draw(&wtb, wcol); + +} + + +static void widget_menubut(uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign) +{ + uiWidgetBase wtb; + + widget_init(&wtb); + + /* half rounded */ + round_box_edges(&wtb, roundboxalign, rect, 4.0f); + + /* decoration */ + widget_menu_trias(&wtb.tria1, rect); + + widgetbase_draw(&wtb, wcol); + + /* text space */ + rect->xmax -= (rect->ymax-rect->ymin); + +} + +static void widget_pulldownbut(uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign) +{ + if(state & UI_ACTIVE) { + uiWidgetBase wtb; + float rad= 0.5f*(rect->ymax - rect->ymin); + + widget_init(&wtb); + + /* fully rounded */ + round_box_edges(&wtb, roundboxalign, rect, rad); + + widgetbase_draw(&wtb, wcol); + } +} + +static void widget_menu_itembut(uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign) +{ + uiWidgetBase wtb; + + widget_init(&wtb); + + /* not rounded, no outline */ + wtb.outline= 0; + round_box_edges(&wtb, 0, rect, 0.0f); + + widgetbase_draw(&wtb, wcol); +} + + +static void widget_optionbut(uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign) +{ + uiWidgetBase wtb; + rcti recttemp= *rect; + int delta; + + widget_init(&wtb); + + /* square */ + recttemp.xmax= recttemp.xmin + (recttemp.ymax-recttemp.ymin); + + /* smaller */ + delta= 1 + (recttemp.ymax-recttemp.ymin)/8; + recttemp.xmin+= delta; + recttemp.ymin+= delta; + recttemp.xmax-= delta; + recttemp.ymax-= delta; + + /* half rounded */ + round_box_edges(&wtb, 15, &recttemp, 4.0f); + + /* decoration */ + if(state & UI_SELECT) { + widget_check_trias(&wtb.tria1, &recttemp); + } + + widgetbase_draw(&wtb, wcol); + + /* text space */ + rect->xmin += (rect->ymax-rect->ymin)*0.7 + delta; +} + + +static void widget_radiobut(uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign) +{ + uiWidgetBase wtb; + + widget_init(&wtb); + + /* half rounded */ + round_box_edges(&wtb, roundboxalign, rect, 4.0f); + + widgetbase_draw(&wtb, wcol); + +} + +static void widget_but(uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign) +{ + uiWidgetBase wtb; + + widget_init(&wtb); + + /* half rounded */ + round_box_edges(&wtb, roundboxalign, rect, 4.0f); + + widgetbase_draw(&wtb, wcol); + +} + +static void widget_roundbut(uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign) +{ + uiWidgetBase wtb; + float rad= 0.5f*(rect->ymax - rect->ymin); + + widget_init(&wtb); + + /* fully rounded */ + round_box_edges(&wtb, roundboxalign, rect, rad); + + widgetbase_draw(&wtb, wcol); +} + +static void widget_draw_extra_mask(const bContext *C, uiBut *but, uiWidgetType *wt, rcti *rect) +{ + uiWidgetBase wtb; + char col[4]; + + /* state copy! */ + wt->wcol= *(wt->wcol_theme); + + widget_init(&wtb); + + if(but->block->drawextra) { + /* note: drawextra can change rect +1 or -1, to match round errors of existing previews */ + but->block->drawextra(C, but->poin, rect); + + /* make mask to draw over image */ + UI_GetThemeColor3ubv(TH_BACK, col); + glColor3ubv(col); + + round_box__edges(&wtb, 15, rect, 0.0f, 4.0); + widgetbase_outline(&wtb); + } + + /* outline */ + round_box_edges(&wtb, 15, rect, 5.0f); + wtb.outline= 1; + wtb.inner= 0; + widgetbase_draw(&wtb, &wt->wcol); + +} + + +static void widget_disabled(rcti *rect) +{ + float col[4]; + + glEnable(GL_BLEND); + + /* can't use theme TH_BACK or TH_PANEL... undefined */ + glGetFloatv(GL_COLOR_CLEAR_VALUE, col); + glColor4f(col[0], col[1], col[2], 0.5f); + glRectf(rect->xmin, rect->ymin, rect->xmax, rect->ymax); + + glDisable(GL_BLEND); +} + +static uiWidgetType *widget_type(uiWidgetTypeEnum type) +{ + bTheme *btheme= U.themes.first; + static uiWidgetType wt; + + /* defaults */ + wt.wcol_theme= &btheme->tui.wcol_regular; + wt.state= widget_state; + wt.draw= widget_but; + wt.custom= NULL; + wt.text= widget_draw_text_icon; + + switch(type) { + case UI_WTYPE_LABEL: + wt.draw= NULL; + wt.state= widget_state_label; + break; + + case UI_WTYPE_TOGGLE: + break; + + case UI_WTYPE_OPTION: + wt.wcol_theme= &btheme->tui.wcol_option; + wt.draw= widget_optionbut; + wt.state= widget_state_label; + break; + + case UI_WTYPE_RADIO: + wt.wcol_theme= &btheme->tui.wcol_radio; + wt.draw= widget_radiobut; + break; + + case UI_WTYPE_NUMBER: + wt.wcol_theme= &btheme->tui.wcol_num; + wt.draw= widget_numbut; + break; + + case UI_WTYPE_SLIDER: + wt.wcol_theme= &btheme->tui.wcol_numslider; + wt.custom= widget_numslider; + break; + + case UI_WTYPE_EXEC: + wt.wcol_theme= &btheme->tui.wcol_tool; + wt.draw= widget_roundbut; + break; + + + /* strings */ + case UI_WTYPE_NAME: + wt.wcol_theme= &btheme->tui.wcol_text; + wt.draw= widget_textbut; + break; + + case UI_WTYPE_NAME_LINK: + break; + + case UI_WTYPE_POINTER_LINK: + break; + + case UI_WTYPE_FILENAME: + break; + + + /* start menus */ + case UI_WTYPE_MENU_RADIO: + wt.wcol_theme= &btheme->tui.wcol_menu; + wt.draw= widget_menubut; + break; + + case UI_WTYPE_MENU_POINTER_LINK: + wt.wcol_theme= &btheme->tui.wcol_menu; + wt.draw= widget_menubut; + break; + + + case UI_WTYPE_PULLDOWN: + wt.wcol_theme= &btheme->tui.wcol_pulldown; + wt.draw= widget_pulldownbut; + wt.state= widget_state_pulldown; + break; + + /* in menus */ + case UI_WTYPE_MENU_ITEM: + wt.wcol_theme= &btheme->tui.wcol_menu_item; + wt.draw= widget_menu_itembut; + wt.state= widget_state_menu_item; + break; + + case UI_WTYPE_MENU_BACK: + wt.wcol_theme= &btheme->tui.wcol_menu_back; + wt.draw= widget_menu_back; + break; + + /* specials */ + case UI_WTYPE_ICON: + wt.draw= NULL; + break; + + case UI_WTYPE_SWATCH: + wt.custom= widget_swatch; + break; + + case UI_WTYPE_BOX: + wt.wcol_theme= &btheme->tui.wcol_box; + break; + + case UI_WTYPE_RGB_PICKER: + break; + + case UI_WTYPE_NORMAL: + break; + } + + return &wt; +} + + +static int widget_roundbox_set(uiBut *but, rcti *rect) +{ + /* alignment */ + if(but->flag & UI_BUT_ALIGN) { + + if(but->flag & UI_BUT_ALIGN_TOP) + rect->ymax+= 1; + if(but->flag & UI_BUT_ALIGN_LEFT) + rect->xmin-= 1; + + switch(but->flag & UI_BUT_ALIGN) { + case UI_BUT_ALIGN_TOP: + return (12); + break; + case UI_BUT_ALIGN_DOWN: + return (3); + break; + case UI_BUT_ALIGN_LEFT: + return (6); + break; + case UI_BUT_ALIGN_RIGHT: + return (9); + break; + + case UI_BUT_ALIGN_DOWN|UI_BUT_ALIGN_RIGHT: + return (1); + break; + case UI_BUT_ALIGN_DOWN|UI_BUT_ALIGN_LEFT: + return (2); + break; + case UI_BUT_ALIGN_TOP|UI_BUT_ALIGN_RIGHT: + return (8); + break; + case UI_BUT_ALIGN_TOP|UI_BUT_ALIGN_LEFT: + return (4); + break; + + default: + return (0); + break; + } + } + return 15; +} + +/* conversion from old to new buttons, so still messy */ +void ui_draw_but(const bContext *C, ARegion *ar, uiStyle *style, uiBut *but, rcti *rect) +{ + bTheme *btheme= U.themes.first; + ThemeUI *tui= &btheme->tui; + uiFontStyle *fstyle= &style->widget; + uiWidgetType *wt= NULL; + + /* handle menus seperately */ + if(but->dt==UI_EMBOSSP) { + switch (but->type) { + case LABEL: + widget_draw_text_icon(&style->widgetlabel, &tui->wcol_menu_back, but, rect); + break; + case SEPR: + break; + + default: + wt= widget_type(UI_WTYPE_MENU_ITEM); + } + } + else if(but->dt==UI_EMBOSSN) { + /* "nothing" */ + wt= widget_type(UI_WTYPE_ICON); + } + else { + + switch (but->type) { + case LABEL: + if(but->block->flag & UI_BLOCK_LOOP) + widget_draw_text_icon(&style->widgetlabel, &tui->wcol_menu_back, but, rect); + else { + wt= widget_type(UI_WTYPE_LABEL); + fstyle= &style->widgetlabel; + } + break; + case SEPR: + break; + case BUT: + wt= widget_type(UI_WTYPE_EXEC); + break; + case NUM: + wt= widget_type(UI_WTYPE_NUMBER); + break; + case NUMSLI: + case HSVSLI: + wt= widget_type(UI_WTYPE_SLIDER); + break; + case ROW: + wt= widget_type(UI_WTYPE_RADIO); + break; + case TEX: + case SEARCH_MENU: + wt= widget_type(UI_WTYPE_NAME); + break; + case TOGBUT: + case TOG: + case TOGN: + case TOG3: + wt= widget_type(UI_WTYPE_TOGGLE); + break; + case OPTION: + case OPTIONN: + if (!(but->flag & UI_HAS_ICON)) { + wt= widget_type(UI_WTYPE_OPTION); + but->flag |= UI_TEXT_LEFT; + } + else + wt= widget_type(UI_WTYPE_TOGGLE); + break; + case MENU: + case BLOCK: + case ICONTEXTROW: + wt= widget_type(UI_WTYPE_MENU_RADIO); + break; + + case PULLDOWN: + wt= widget_type(UI_WTYPE_PULLDOWN); + break; + + case BUTM: + wt= widget_type(UI_WTYPE_MENU_ITEM); + break; + + case COL: + wt= widget_type(UI_WTYPE_SWATCH); + break; + + case ROUNDBOX: + wt= widget_type(UI_WTYPE_BOX); + break; + + case BUT_EXTRA: + widget_draw_extra_mask(C, but, widget_type(UI_WTYPE_BOX), rect); + break; + + // XXX four old button types + case HSVCUBE: + ui_draw_but_HSVCUBE(but, rect); + break; + case BUT_COLORBAND: + ui_draw_but_COLORBAND(but, &tui->wcol_regular, rect); + break; + case BUT_NORMAL: + ui_draw_but_NORMAL(but, &tui->wcol_regular, rect); + break; + case BUT_CURVE: + ui_draw_but_CURVE(ar, but, &tui->wcol_regular, rect); + break; + + default: + wt= widget_type(UI_WTYPE_TOGGLE); + } + } + + if(wt) { + rcti disablerect= *rect; /* rect gets clipped smaller for text */ + int roundboxalign, state; + + roundboxalign= widget_roundbox_set(but, rect); + state= but->flag; + if(but->editstr) state |= UI_TEXTINPUT; + + wt->state(wt, state); + if(wt->custom) + wt->custom(but, &wt->wcol, rect, state, roundboxalign); + else if(wt->draw) + wt->draw(&wt->wcol, rect, state, roundboxalign); + wt->text(fstyle, &wt->wcol, but, rect); + + if(state & (UI_BUT_DISABLED|UI_BUT_INACTIVE)) + if(but->dt!=UI_EMBOSSP) + widget_disabled(&disablerect); + } +} + +void ui_draw_menu_back(uiStyle *style, uiBlock *block, rcti *rect) +{ + uiWidgetType *wt= widget_type(UI_WTYPE_MENU_BACK); + + wt->state(wt, 0); + if(block) + wt->draw(&wt->wcol, rect, block->flag, block->direction); + else + wt->draw(&wt->wcol, rect, 0, 0); + +} + +void ui_draw_search_back(uiStyle *style, uiBlock *block, rcti *rect) +{ + uiWidgetType *wt= widget_type(UI_WTYPE_BOX); + + glEnable(GL_BLEND); + widget_softshadow(rect, 15, 5.0f, 8.0f); + glDisable(GL_BLEND); + + wt->state(wt, 0); + if(block) + wt->draw(&wt->wcol, rect, block->flag, 15); + else + wt->draw(&wt->wcol, rect, 0, 15); + +} + + +/* helper call to draw a menu item without button */ +/* state: UI_ACTIVE or 0 */ +void ui_draw_menu_item(uiFontStyle *fstyle, rcti *rect, char *name, int state) +{ + uiWidgetType *wt= widget_type(UI_WTYPE_MENU_ITEM); + rcti _rect= *rect; + char *cpoin; + + wt->state(wt, state); + wt->draw(&wt->wcol, rect, 0, 0); + + uiStyleFontSet(fstyle); + fstyle->align= UI_STYLE_TEXT_LEFT; + + /* text location offset */ + rect->xmin+=5; + + /* cut string in 2 parts? */ + cpoin= strchr(name, '|'); + if(cpoin) { + *cpoin= 0; + rect->xmax -= BLF_width(cpoin+1) + 10; + } + + glColor3ubv(wt->wcol.text); + uiStyleFontDraw(fstyle, rect, name); + + /* part text right aligned */ + if(cpoin) { + fstyle->align= UI_STYLE_TEXT_RIGHT; + rect->xmax= _rect.xmax - 5; + uiStyleFontDraw(fstyle, rect, cpoin+1); + *cpoin= '|'; + } + + /* restore rect, was messed with */ + *rect= _rect; + +} + diff --git a/source/blender/editors/interface/keyval.c b/source/blender/editors/interface/keyval.c new file mode 100644 index 00000000000..f2172ac8cf0 --- /dev/null +++ b/source/blender/editors/interface/keyval.c @@ -0,0 +1,540 @@ +/** + * $Id$ + * + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + * + * The Original Code is: all of this file. + * + * Contributor(s): none yet. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include "stdio.h" +#include "ctype.h" +#include "string.h" + +#include "BKE_global.h" +#include "BLI_blenlib.h" +#include "BLI_arithb.h" + +#include "WM_types.h" + +char *key_event_to_string(unsigned short event) +{ + + switch(event) { + case AKEY: + return "A"; + break; + case BKEY: + return "B"; + break; + case CKEY: + return "C"; + break; + case DKEY: + return "D"; + break; + case EKEY: + return "E"; + break; + case FKEY: + return "F"; + break; + case GKEY: + return "G"; + break; + case HKEY: + return "H"; + break; + case IKEY: + return "I"; + break; + case JKEY: + return "J"; + break; + case KKEY: + return "K"; + break; + case LKEY: + return "L"; + break; + case MKEY: + return "M"; + break; + case NKEY: + return "N"; + break; + case OKEY: + return "O"; + break; + case PKEY: + return "P"; + break; + case QKEY: + return "Q"; + break; + case RKEY: + return "R"; + break; + case SKEY: + return "S"; + break; + case TKEY: + return "T"; + break; + case UKEY: + return "U"; + break; + case VKEY: + return "V"; + break; + case WKEY: + return "W"; + break; + case XKEY: + return "X"; + break; + case YKEY: + return "Y"; + break; + case ZKEY: + return "Z"; + break; + + case ZEROKEY: + return "Zero"; + break; + case ONEKEY: + return "One"; + break; + case TWOKEY: + return "Two"; + break; + case THREEKEY: + return "Three"; + break; + case FOURKEY: + return "Four"; + break; + case FIVEKEY: + return "Five"; + break; + case SIXKEY: + return "Six"; + break; + case SEVENKEY: + return "Seven"; + break; + case EIGHTKEY: + return "Eight"; + break; + case NINEKEY: + return "Nine"; + break; + + case LEFTCTRLKEY: + return "Leftctrl"; + break; + case LEFTALTKEY: + return "Leftalt"; + break; + case RIGHTALTKEY: + return "Rightalt"; + break; + case RIGHTCTRLKEY: + return "Rightctrl"; + break; + case RIGHTSHIFTKEY: + return "Rightshift"; + break; + case LEFTSHIFTKEY: + return "Leftshift"; + break; + + case ESCKEY: + return "Esc"; + break; + case TABKEY: + return "Tab"; + break; + case RETKEY: + return "Ret"; + break; + case SPACEKEY: + return "Space"; + break; + case LINEFEEDKEY: + return "Linefeed"; + break; + case BACKSPACEKEY: + return "Backspace"; + break; + case DELKEY: + return "Del"; + break; + case SEMICOLONKEY: + return "Semicolon"; + break; + case PERIODKEY: + return "Period"; + break; + case COMMAKEY: + return "Comma"; + break; + case QUOTEKEY: + return "Quote"; + break; + case ACCENTGRAVEKEY: + return "Accentgrave"; + break; + case MINUSKEY: + return "Minus"; + break; + case SLASHKEY: + return "Slash"; + break; + case BACKSLASHKEY: + return "Backslash"; + break; + case EQUALKEY: + return "Equal"; + break; + case LEFTBRACKETKEY: + return "Leftbracket"; + break; + case RIGHTBRACKETKEY: + return "Rightbracket"; + break; + + case LEFTARROWKEY: + return "Leftarrow"; + break; + case DOWNARROWKEY: + return "Downarrow"; + break; + case RIGHTARROWKEY: + return "Rightarrow"; + break; + case UPARROWKEY: + return "Uparrow"; + break; + + case PAD2: + return "Pad2"; + break; + case PAD4: + return "Pad4"; + break; + case PAD6: + return "Pad6"; + break; + case PAD8: + return "Pad8"; + break; + case PAD1: + return "Pad1"; + break; + case PAD3: + return "Pad3"; + break; + case PAD5: + return "Pad5"; + break; + case PAD7: + return "Pad7"; + break; + case PAD9: + return "Pad9"; + break; + + case PADPERIOD: + return "Padperiod"; + break; + case PADSLASHKEY: + return "Padslash"; + break; + case PADASTERKEY: + return "Padaster"; + break; + + case PAD0: + return "Pad0"; + break; + case PADMINUS: + return "Padminus"; + break; + case PADENTER: + return "Padenter"; + break; + case PADPLUSKEY: + return "Padplus"; + break; + + case F1KEY: + return "F1"; + break; + case F2KEY: + return "F2"; + break; + case F3KEY: + return "F3"; + break; + case F4KEY: + return "F4"; + break; + case F5KEY: + return "F5"; + break; + case F6KEY: + return "F6"; + break; + case F7KEY: + return "F7"; + break; + case F8KEY: + return "F8"; + break; + case F9KEY: + return "F9"; + break; + case F10KEY: + return "F10"; + break; + case F11KEY: + return "F11"; + break; + case F12KEY: + return "F12"; + break; + + case PAUSEKEY: + return "Pause"; + break; + case INSERTKEY: + return "Insert"; + break; + case HOMEKEY: + return "Home"; + break; + case PAGEUPKEY: + return "Pageup"; + break; + case PAGEDOWNKEY: + return "Pagedown"; + break; + case ENDKEY: + return "End"; + break; + } + + return ""; +} + +/* + * Decodes key combination strings [qual1+[qual2+[...]]]keyname + * The '+'s may be replaced by '-' or ' ' characters to support different + * formats. No additional whitespace is allowed. The keyname may be an internal + * name, like "RETKEY", or a more common name, like "Return". Decoding is case- + * insensitive. + * + * Example strings: "Ctrl+L", "ALT-ESC", "Shift A" + * + * Returns 1 if successful. + */ +int decode_key_string(char *str, unsigned short *key, unsigned short *qual) +{ + int i, prev, len, invalid=0; + + len= strlen(str); + *key= *qual= 0; + + /* Convert to upper case */ + for (i=0; i<len; i++) { + str[i]= toupper(str[i]); + } + + /* Handle modifiers */ + for (prev=i=0; i<len; i++) { + if (str[i]==' ' || str[i]=='+' || str[i]=='-') { +// XXX if (!strncmp(str+prev, "CTRL", i-prev)) *qual |= LR_CTRLKEY; +// else if (!strncmp(str+prev, "ALT", i-prev)) *qual |= LR_ALTKEY; +// else if (!strncmp(str+prev, "SHIFT", i-prev)) *qual |= LR_SHIFTKEY; +// else if (!strncmp(str+prev, "COMMAND", i-prev)) *qual |= LR_COMMANDKEY; + prev=i+1; + } + } + + /* Compare last part against key names */ + if ((len-prev==1) || ((len-prev==4) && !strncmp(str+prev, "KEY", 3))) { + + if (str[prev]>='A' && str[prev]<='Z') { + *key= str[prev]-'A'+AKEY; + } else if (str[prev]>='0' && str[prev]<='9') { + *key= str[prev]-'0'+ZEROKEY; + } else { + invalid= 1; + } + + } else if (!strncmp(str+prev, "ZEROKEY", len-prev) || !strncmp(str+prev, "ZERO", len-prev)) { + *key= ZEROKEY; + } else if (!strncmp(str+prev, "ONEKEY", len-prev) || !strncmp(str+prev, "ONE", len-prev)) { + *key= ONEKEY; + } else if (!strncmp(str+prev, "TWOKEY", len-prev) || !strncmp(str+prev, "TWO", len-prev)) { + *key= TWOKEY; + } else if (!strncmp(str+prev, "THREEKEY", len-prev) || !strncmp(str+prev, "THREE", len-prev)) { + *key= THREEKEY; + } else if (!strncmp(str+prev, "FOURKEY", len-prev) || !strncmp(str+prev, "FOUR", len-prev)) { + *key= FOURKEY; + } else if (!strncmp(str+prev, "FIVEKEY", len-prev) || !strncmp(str+prev, "FIVE", len-prev)) { + *key= FIVEKEY; + } else if (!strncmp(str+prev, "SIZEKEY", len-prev) || !strncmp(str+prev, "SIX", len-prev)) { + *key= SIXKEY; + } else if (!strncmp(str+prev, "SEVENKEY", len-prev) || !strncmp(str+prev, "SEVEN", len-prev)) { + *key= SEVENKEY; + } else if (!strncmp(str+prev, "EIGHTKEY", len-prev) || !strncmp(str+prev, "EIGHT", len-prev)) { + *key= EIGHTKEY; + } else if (!strncmp(str+prev, "NINEKEY", len-prev) || !strncmp(str+prev, "NINE", len-prev)) { + *key= NINEKEY; + + } else if (!strncmp(str+prev, "ESCKEY", len-prev) || !strncmp(str+prev, "ESC", len-prev)) { + *key= ESCKEY; + } else if (!strncmp(str+prev, "TABKEY", len-prev) || !strncmp(str+prev, "TAB", len-prev)) { + *key= TABKEY; + } else if (!strncmp(str+prev, "RETKEY", len-prev) || !strncmp(str+prev, "RETURN", len-prev) || !strncmp(str+prev, "ENTER", len-prev)) { + *key= RETKEY; + } else if (!strncmp(str+prev, "SPACEKEY", len-prev) || !strncmp(str+prev, "SPACE", len-prev)) { + *key= SPACEKEY; + } else if (!strncmp(str+prev, "LINEFEEDKEY", len-prev) || !strncmp(str+prev, "LINEFEED", len-prev)) { + *key= LINEFEEDKEY; + } else if (!strncmp(str+prev, "BACKSPACEKEY", len-prev) || !strncmp(str+prev, "BACKSPACE", len-prev)) { + *key= BACKSPACEKEY; + } else if (!strncmp(str+prev, "DELKEY", len-prev) || !strncmp(str+prev, "DELETE", len-prev)) { + *key= DELKEY; + + } else if (!strncmp(str+prev, "SEMICOLONKEY", len-prev) || !strncmp(str+prev, "SEMICOLON", len-prev)) { + *key= SEMICOLONKEY; + } else if (!strncmp(str+prev, "PERIODKEY", len-prev) || !strncmp(str+prev, "PERIOD", len-prev)) { + *key= PERIODKEY; + } else if (!strncmp(str+prev, "COMMAKEY", len-prev) || !strncmp(str+prev, "COMMA", len-prev)) { + *key= COMMAKEY; + } else if (!strncmp(str+prev, "QUOTEKEY", len-prev) || !strncmp(str+prev, "QUOTE", len-prev)) { + *key= QUOTEKEY; + } else if (!strncmp(str+prev, "ACCENTGRAVEKEY", len-prev) || !strncmp(str+prev, "ACCENTGRAVE", len-prev)) { + *key= ACCENTGRAVEKEY; + } else if (!strncmp(str+prev, "MINUSKEY", len-prev) || !strncmp(str+prev, "MINUS", len-prev)) { + *key= MINUSKEY; + } else if (!strncmp(str+prev, "SLASHKEY", len-prev) || !strncmp(str+prev, "SLASH", len-prev)) { + *key= SLASHKEY; + } else if (!strncmp(str+prev, "BACKSLASHKEY", len-prev) || !strncmp(str+prev, "BACKSLASH", len-prev)) { + *key= BACKSLASHKEY; + } else if (!strncmp(str+prev, "EQUALKEY", len-prev) || !strncmp(str+prev, "EQUAL", len-prev)) { + *key= EQUALKEY; + } else if (!strncmp(str+prev, "LEFTBRACKETKEY", len-prev) || !strncmp(str+prev, "LEFTBRACKET", len-prev)) { + *key= LEFTBRACKETKEY; + } else if (!strncmp(str+prev, "RIGHTBRACKETKEY", len-prev) || !strncmp(str+prev, "RIGHTBRACKET", len-prev)) { + *key= RIGHTBRACKETKEY; + } else if (!strncmp(str+prev, "DELKEY", len-prev) || !strncmp(str+prev, "DELETE", len-prev)) { + *key= DELKEY; + + } else if (!strncmp(str+prev, "LEFTARROWKEY", len-prev) || !strncmp(str+prev, "LEFTARROW", len-prev)) { + *key= LEFTARROWKEY; + } else if (!strncmp(str+prev, "DOWNARROWKEY", len-prev) || !strncmp(str+prev, "DOWNARROW", len-prev)) { + *key= DOWNARROWKEY; + } else if (!strncmp(str+prev, "RIGHTARROWKEY", len-prev) || !strncmp(str+prev, "RIGHTARROW", len-prev)) { + *key= RIGHTARROWKEY; + } else if (!strncmp(str+prev, "UPARROWKEY", len-prev) || !strncmp(str+prev, "UPARROW", len-prev)) { + *key= UPARROWKEY; + + } else if (!strncmp(str+prev, "PAD", 3)) { + + if (len-prev<=4) { + + if (str[prev]>='0' && str[prev]<='9') { + *key= str[prev]-'0'+ZEROKEY; + } else { + invalid= 1; + } + + } else if (!strncmp(str+prev+3, "PERIODKEY", len-prev-3) || !strncmp(str+prev+3, "PERIOD", len-prev-3)) { + *key= PADPERIOD; + } else if (!strncmp(str+prev+3, "SLASHKEY", len-prev-3) || !strncmp(str+prev+3, "SLASH", len-prev-3)) { + *key= PADSLASHKEY; + } else if (!strncmp(str+prev+3, "ASTERKEY", len-prev-3) || !strncmp(str+prev+3, "ASTERISK", len-prev-3)) { + *key= PADASTERKEY; + } else if (!strncmp(str+prev+3, "MINUSKEY", len-prev-3) || !strncmp(str+prev+3, "MINUS", len-prev-3)) { + *key= PADMINUS; + } else if (!strncmp(str+prev+3, "ENTERKEY", len-prev-3) || !strncmp(str+prev+3, "ENTER", len-prev-3)) { + *key= PADENTER; + } else if (!strncmp(str+prev+3, "PLUSKEY", len-prev-3) || !strncmp(str+prev+3, "PLUS", len-prev-3)) { + *key= PADPLUSKEY; + } else { + invalid= 1; + } + + } else if (!strncmp(str+prev, "F1KEY", len-prev) || !strncmp(str+prev, "F1", len-prev)) { + *key= F1KEY; + } else if (!strncmp(str+prev, "F2KEY", len-prev) || !strncmp(str+prev, "F2", len-prev)) { + *key= F2KEY; + } else if (!strncmp(str+prev, "F3KEY", len-prev) || !strncmp(str+prev, "F3", len-prev)) { + *key= F3KEY; + } else if (!strncmp(str+prev, "F4KEY", len-prev) || !strncmp(str+prev, "F4", len-prev)) { + *key= F4KEY; + } else if (!strncmp(str+prev, "F5KEY", len-prev) || !strncmp(str+prev, "F5", len-prev)) { + *key= F5KEY; + } else if (!strncmp(str+prev, "F6KEY", len-prev) || !strncmp(str+prev, "F6", len-prev)) { + *key= F6KEY; + } else if (!strncmp(str+prev, "F7KEY", len-prev) || !strncmp(str+prev, "F7", len-prev)) { + *key= F7KEY; + } else if (!strncmp(str+prev, "F8KEY", len-prev) || !strncmp(str+prev, "F8", len-prev)) { + *key= F8KEY; + } else if (!strncmp(str+prev, "F9KEY", len-prev) || !strncmp(str+prev, "F9", len-prev)) { + *key= F9KEY; + } else if (!strncmp(str+prev, "F10KEY", len-prev) || !strncmp(str+prev, "F10", len-prev)) { + *key= F10KEY; + } else if (!strncmp(str+prev, "F11KEY", len-prev) || !strncmp(str+prev, "F11", len-prev)) { + *key= F11KEY; + } else if (!strncmp(str+prev, "F12KEY", len-prev) || !strncmp(str+prev, "F12", len-prev)) { + *key= F12KEY; + + } else if (!strncmp(str+prev, "PAUSEKEY", len-prev) || !strncmp(str+prev, "PAUSE", len-prev)) { + *key= PAUSEKEY; + } else if (!strncmp(str+prev, "INSERTKEY", len-prev) || !strncmp(str+prev, "INSERT", len-prev)) { + *key= INSERTKEY; + } else if (!strncmp(str+prev, "HOMEKEY", len-prev) || !strncmp(str+prev, "HOME", len-prev)) { + *key= HOMEKEY; + } else if (!strncmp(str+prev, "PAGEUPKEY", len-prev) || !strncmp(str+prev, "PAGEUP", len-prev)) { + *key= PAGEUPKEY; + } else if (!strncmp(str+prev, "PAGEDOWNKEY", len-prev) || !strncmp(str+prev, "PAGEDOWN", len-prev)) { + *key= PAGEDOWNKEY; + } else if (!strncmp(str+prev, "ENDKEY", len-prev) || !strncmp(str+prev, "END", len-prev)) { + *key= ENDKEY; + + } else { + invalid= 1; + } + + if (!invalid && *key) { + return 1; + } + + return 0; +} diff --git a/source/blender/editors/interface/resources.c b/source/blender/editors/interface/resources.c new file mode 100644 index 00000000000..1cb58c986d0 --- /dev/null +++ b/source/blender/editors/interface/resources.c @@ -0,0 +1,1259 @@ +/** + * $Id: resources.c 12755 2007-12-02 05:50:38Z aligorith $ + * + * ***** BEGIN GPL/BL DUAL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. The Blender + * Foundation also sells licenses for use in proprietary software under + * the Blender License. See http://www.blender.org/BL/ for information + * about this. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + * + * The Original Code is: all of this file. + * + * Contributor(s): none yet. + * + * ***** END GPL/BL DUAL LICENSE BLOCK ***** + */ + +#include <math.h> +#include <stdlib.h> +#include <string.h> + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "MEM_guardedalloc.h" + + +#include "DNA_curve_types.h" +#include "DNA_listBase.h" +#include "DNA_userdef_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "BLI_blenlib.h" + +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" + +#include "BKE_DerivedMesh.h" +#include "BKE_global.h" +#include "BKE_main.h" +#include "BKE_texture.h" +#include "BKE_utildefines.h" + +#include "BIF_gl.h" + +#include "UI_interface.h" +#include "UI_resources.h" +#include "UI_interface_icons.h" + +#include "interface_intern.h" + +/* global for themes */ +typedef void (*VectorDrawFunc)(int x, int y, int w, int h, float alpha); + +static bTheme *theme_active=NULL; +static int theme_spacetype= SPACE_VIEW3D; +static int theme_regionid= RGN_TYPE_WINDOW; + +void ui_resources_init(void) +{ + UI_icons_init(BIFICONID_LAST+1); +} + +void ui_resources_free(void) +{ + UI_icons_free(); +} + + +/* ******************************************************** */ +/* THEMES */ +/* ******************************************************** */ + +char *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid) +{ + ThemeSpace *ts= NULL; + static char error[4]={240, 0, 240, 255}; + static char alert[4]={240, 60, 60, 255}; + static char headerdesel[4]={0,0,0,255}; + + char *cp= error; + + if(btheme) { + + // first check for ui buttons theme + if(colorid < TH_THEMEUI) { + + switch(colorid) { + + case TH_REDALERT: + cp= alert; break; + } + } + else { + + switch(spacetype) { + case SPACE_BUTS: + ts= &btheme->tbuts; + break; + case SPACE_VIEW3D: + ts= &btheme->tv3d; + break; + case SPACE_IPO: + ts= &btheme->tipo; + break; + case SPACE_FILE: + ts= &btheme->tfile; + break; + case SPACE_NLA: + ts= &btheme->tnla; + break; + case SPACE_ACTION: + ts= &btheme->tact; + break; + case SPACE_SEQ: + ts= &btheme->tseq; + break; + case SPACE_IMAGE: + ts= &btheme->tima; + break; + case SPACE_IMASEL: + ts= &btheme->timasel; + break; + case SPACE_TEXT: + ts= &btheme->text; + break; + case SPACE_OUTLINER: + ts= &btheme->toops; + break; + case SPACE_SOUND: + ts= &btheme->tsnd; + break; + case SPACE_INFO: + ts= &btheme->tinfo; + break; + case SPACE_TIME: + ts= &btheme->ttime; + break; + case SPACE_NODE: + ts= &btheme->tnode; + break; + default: + ts= &btheme->tv3d; + break; + } + + switch(colorid) { + case TH_BACK: + if(theme_regionid==RGN_TYPE_WINDOW) + cp= ts->back; + else if(theme_regionid==RGN_TYPE_CHANNELS) + cp= ts->list; + else if(theme_regionid==RGN_TYPE_HEADER) + cp= ts->header; + else + cp= ts->button; + break; + case TH_TEXT: + if(theme_regionid==RGN_TYPE_WINDOW) + cp= ts->text; + else if(theme_regionid==RGN_TYPE_CHANNELS) + cp= ts->list_text; + else if(theme_regionid==RGN_TYPE_HEADER) + cp= ts->header_text; + else + cp= ts->button_text; + break; + case TH_TEXT_HI: + if(theme_regionid==RGN_TYPE_WINDOW) + cp= ts->text_hi; + else if(theme_regionid==RGN_TYPE_CHANNELS) + cp= ts->list_text_hi; + else if(theme_regionid==RGN_TYPE_HEADER) + cp= ts->header_text_hi; + else + cp= ts->button_text_hi; + break; + case TH_TITLE: + if(theme_regionid==RGN_TYPE_WINDOW) + cp= ts->title; + else if(theme_regionid==RGN_TYPE_CHANNELS) + cp= ts->list_title; + else if(theme_regionid==RGN_TYPE_HEADER) + cp= ts->header_title; + else + cp= ts->button_title; + break; + + case TH_HEADER: + cp= ts->header; break; + case TH_HEADERDESEL: + /* we calculate a dynamic builtin header deselect color, also for pulldowns... */ + cp= ts->header; + headerdesel[0]= cp[0]>10?cp[0]-10:0; + headerdesel[1]= cp[1]>10?cp[1]-10:0; + headerdesel[2]= cp[2]>10?cp[2]-10:0; + cp= headerdesel; + break; + case TH_HEADER_TEXT: + cp= ts->header_text; break; + case TH_HEADER_TEXT_HI: + cp= ts->header_text_hi; break; + + case TH_PANEL: + cp= ts->panel; break; + case TH_PANEL_TEXT: + cp= ts->panel_text; break; + case TH_PANEL_TEXT_HI: + cp= ts->panel_text_hi; break; + + case TH_BUTBACK: + cp= ts->button; break; + case TH_BUTBACK_TEXT: + cp= ts->button_text; break; + case TH_BUTBACK_TEXT_HI: + cp= ts->button_text_hi; break; + + case TH_SHADE1: + cp= ts->shade1; break; + case TH_SHADE2: + cp= ts->shade2; break; + case TH_HILITE: + cp= ts->hilite; break; + + case TH_GRID: + cp= ts->grid; break; + case TH_WIRE: + cp= ts->wire; break; + case TH_LAMP: + cp= ts->lamp; break; + case TH_SELECT: + cp= ts->select; break; + case TH_ACTIVE: + cp= ts->active; break; + case TH_GROUP: + cp= ts->group; break; + case TH_GROUP_ACTIVE: + cp= ts->group_active; break; + case TH_TRANSFORM: + cp= ts->transform; break; + case TH_VERTEX: + cp= ts->vertex; break; + case TH_VERTEX_SELECT: + cp= ts->vertex_select; break; + case TH_VERTEX_SIZE: + cp= &ts->vertex_size; break; + case TH_EDGE: + cp= ts->edge; break; + case TH_EDGE_SELECT: + cp= ts->edge_select; break; + case TH_EDGE_SEAM: + cp= ts->edge_seam; break; + case TH_EDGE_SHARP: + cp= ts->edge_sharp; break; + case TH_EDITMESH_ACTIVE: + cp= ts->editmesh_active; break; + case TH_EDGE_FACESEL: + cp= ts->edge_facesel; break; + case TH_FACE: + cp= ts->face; break; + case TH_FACE_SELECT: + cp= ts->face_select; break; + case TH_FACE_DOT: + cp= ts->face_dot; break; + case TH_FACEDOT_SIZE: + cp= &ts->facedot_size; break; + case TH_NORMAL: + cp= ts->normal; break; + case TH_BONE_SOLID: + cp= ts->bone_solid; break; + case TH_BONE_POSE: + cp= ts->bone_pose; break; + case TH_STRIP: + cp= ts->strip; break; + case TH_STRIP_SELECT: + cp= ts->strip_select; break; + case TH_CFRAME: + cp= ts->cframe; break; + + case TH_SYNTAX_B: + cp= ts->syntaxb; break; + case TH_SYNTAX_V: + cp= ts->syntaxv; break; + case TH_SYNTAX_C: + cp= ts->syntaxc; break; + case TH_SYNTAX_L: + cp= ts->syntaxl; break; + case TH_SYNTAX_N: + cp= ts->syntaxn; break; + + case TH_NODE: + cp= ts->syntaxl; break; + case TH_NODE_IN_OUT: + cp= ts->syntaxn; break; + case TH_NODE_OPERATOR: + cp= ts->syntaxb; break; + case TH_NODE_CONVERTOR: + cp= ts->syntaxv; break; + case TH_NODE_GROUP: + cp= ts->syntaxc; break; + + case TH_SEQ_MOVIE: + cp= ts->movie; break; + case TH_SEQ_IMAGE: + cp= ts->image; break; + case TH_SEQ_SCENE: + cp= ts->scene; break; + case TH_SEQ_AUDIO: + cp= ts->audio; break; + case TH_SEQ_EFFECT: + cp= ts->effect; break; + case TH_SEQ_PLUGIN: + cp= ts->plugin; break; + case TH_SEQ_TRANSITION: + cp= ts->transition; break; + case TH_SEQ_META: + cp= ts->meta; break; + + case TH_HANDLE_VERTEX: + cp= ts->handle_vertex; + break; + case TH_HANDLE_VERTEX_SELECT: + cp= ts->handle_vertex_select; + break; + case TH_HANDLE_VERTEX_SIZE: + cp= &ts->handle_vertex_size; + break; + + case TH_DOPESHEET_CHANNELOB: + cp= ts->ds_channel; + break; + case TH_DOPESHEET_CHANNELSUBOB: + cp= ts->ds_subchannel; + break; + + } + } + } + + return cp; +} + +#define SETCOLTEST(col, r, g, b, a) if(col[3]==0) {col[0]=r; col[1]=g; col[2]= b; col[3]= a;} + +/* use this call to init new variables in themespace, if they're same for all */ +static void ui_theme_init_new_do(ThemeSpace *ts) +{ + SETCOLTEST(ts->header_text, 0, 0, 0, 255); + SETCOLTEST(ts->header_title, 0, 0, 0, 255); + SETCOLTEST(ts->header_text_hi, 255, 255, 255, 255); + + SETCOLTEST(ts->panel_text, 0, 0, 0, 255); + SETCOLTEST(ts->panel_title, 0, 0, 0, 255); + SETCOLTEST(ts->panel_text_hi, 255, 255, 255, 255); + + SETCOLTEST(ts->button, 145, 145, 145, 245); + SETCOLTEST(ts->button_title, 0, 0, 0, 255); + SETCOLTEST(ts->button_text, 0, 0, 0, 255); + SETCOLTEST(ts->button_text_hi, 255, 255, 255, 255); + + SETCOLTEST(ts->list, 165, 165, 165, 255); + SETCOLTEST(ts->list_title, 0, 0, 0, 255); + SETCOLTEST(ts->list_text, 0, 0, 0, 255); + SETCOLTEST(ts->list_text_hi, 255, 255, 255, 255); +} + +static void ui_theme_init_new(bTheme *btheme) +{ + ui_theme_init_new_do(&btheme->tbuts); + ui_theme_init_new_do(&btheme->tv3d); + ui_theme_init_new_do(&btheme->tfile); + ui_theme_init_new_do(&btheme->tipo); + ui_theme_init_new_do(&btheme->tinfo); + ui_theme_init_new_do(&btheme->tsnd); + ui_theme_init_new_do(&btheme->tact); + ui_theme_init_new_do(&btheme->tnla); + ui_theme_init_new_do(&btheme->tseq); + ui_theme_init_new_do(&btheme->tima); + ui_theme_init_new_do(&btheme->timasel); + ui_theme_init_new_do(&btheme->text); + ui_theme_init_new_do(&btheme->toops); + ui_theme_init_new_do(&btheme->ttime); + ui_theme_init_new_do(&btheme->tnode); + +} + +#define SETCOL(col, r, g, b, a) col[0]=r; col[1]=g; col[2]= b; col[3]= a; +#define SETCOLF(col, r, g, b, a) col[0]=r*255; col[1]=g*255; col[2]= b*255; col[3]= a*255; + +/* initialize default theme, can't be edited + Note: when you add new colors, created & saved themes need initialized + use function below, init_userdef_do_versions() +*/ +void ui_theme_init_userdef(void) +{ + bTheme *btheme= U.themes.first; + + /* we search for the theme with name Default */ + for(btheme= U.themes.first; btheme; btheme= btheme->next) { + if(strcmp("Default", btheme->name)==0) break; + } + + if(btheme==NULL) { + btheme= MEM_callocN(sizeof(bTheme), "theme"); + BLI_addtail(&U.themes, btheme); + strcpy(btheme->name, "Default"); + } + + UI_SetTheme(0, 0); // make sure the global used in this file is set + + /* UI buttons */ + ui_widget_color_init(&btheme->tui); + + /* common (new) variables */ + ui_theme_init_new(btheme); + + /* space view3d */ + SETCOLF(btheme->tv3d.back, 0.225, 0.225, 0.225, 1.0); + SETCOL(btheme->tv3d.text, 0, 0, 0, 255); + SETCOL(btheme->tv3d.text_hi, 255, 255, 255, 255); + + SETCOLF(btheme->tv3d.header, 0.45, 0.45, 0.45, 1.0); + SETCOL(btheme->tv3d.panel, 165, 165, 165, 127); + + SETCOL(btheme->tv3d.shade1, 160, 160, 160, 100); + SETCOL(btheme->tv3d.shade2, 0x7f, 0x70, 0x70, 100); + + SETCOLF(btheme->tv3d.grid, 0.251, 0.251, 0.251, 1.0); + SETCOL(btheme->tv3d.wire, 0x0, 0x0, 0x0, 255); + SETCOL(btheme->tv3d.lamp, 0, 0, 0, 40); + SETCOL(btheme->tv3d.select, 241, 88, 0, 255); + SETCOL(btheme->tv3d.active, 255, 140, 25, 255); + SETCOL(btheme->tv3d.group, 16, 64, 16, 255); + SETCOL(btheme->tv3d.group_active, 85, 187, 85, 255); + SETCOL(btheme->tv3d.transform, 0xff, 0xff, 0xff, 255); + SETCOL(btheme->tv3d.vertex, 0, 0, 0, 255); + SETCOL(btheme->tv3d.vertex_select, 255, 133, 0, 255); + btheme->tv3d.vertex_size= 3; + SETCOL(btheme->tv3d.edge, 0x0, 0x0, 0x0, 255); + SETCOL(btheme->tv3d.edge_select, 255, 160, 0, 255); + SETCOL(btheme->tv3d.edge_seam, 219, 37, 18, 255); + SETCOL(btheme->tv3d.edge_facesel, 75, 75, 75, 255); + SETCOL(btheme->tv3d.face, 0, 0, 0, 18); + SETCOL(btheme->tv3d.face_select, 255, 133, 0, 60); + SETCOL(btheme->tv3d.normal, 0x22, 0xDD, 0xDD, 255); + SETCOL(btheme->tv3d.face_dot, 255, 133, 0, 255); + btheme->tv3d.facedot_size= 4; + SETCOL(btheme->tv3d.cframe, 0x60, 0xc0, 0x40, 255); + + SETCOL(btheme->tv3d.bone_solid, 200, 200, 200, 255); + SETCOL(btheme->tv3d.bone_pose, 80, 200, 255, 80); // alpha 80 is not meant editable, used for wire+action draw + + + /* space buttons */ + /* to have something initialized */ + btheme->tbuts= btheme->tv3d; + + SETCOLF(btheme->tbuts.back, 0.45, 0.45, 0.45, 1.0); + SETCOL(btheme->tbuts.panel, 0x82, 0x82, 0x82, 255); + + /* graph editor */ + btheme->tipo= btheme->tv3d; + SETCOLF(btheme->tipo.back, 0.42, 0.42, 0.42, 1.0); + SETCOLF(btheme->tipo.list, 0.4, 0.4, 0.4, 1.0); + SETCOL(btheme->tipo.grid, 94, 94, 94, 255); + SETCOL(btheme->tipo.panel, 255, 255, 255, 150); + SETCOL(btheme->tipo.shade1, 150, 150, 150, 100); /* scrollbars */ + SETCOL(btheme->tipo.shade2, 0x70, 0x70, 0x70, 100); + SETCOL(btheme->tipo.vertex, 0, 0, 0, 255); + SETCOL(btheme->tipo.vertex_select, 255, 133, 0, 255); + SETCOL(btheme->tipo.hilite, 0x60, 0xc0, 0x40, 255); + btheme->tipo.vertex_size= 3; + + SETCOL(btheme->tipo.handle_vertex, 0, 0, 0, 255); + SETCOL(btheme->tipo.handle_vertex_select, 255, 133, 0, 255); + btheme->tipo.handle_vertex_size= 3; + + SETCOL(btheme->tipo.ds_channel, 82, 96, 110, 255); + SETCOL(btheme->tipo.ds_subchannel, 124, 137, 150, 255); + SETCOL(btheme->tipo.group, 79, 101, 73, 255); + SETCOL(btheme->tipo.group_active, 135, 177, 125, 255); + + /* dopesheet */ + btheme->tact= btheme->tipo; + SETCOL(btheme->tact.strip, 12, 10, 10, 128); + SETCOL(btheme->tact.strip_select, 255, 140, 0, 255); + + /* space file */ + /* to have something initialized */ + btheme->tfile= btheme->tv3d; + SETCOL(btheme->tfile.back, 90, 90, 90, 255); + SETCOL(btheme->tfile.text, 250, 250, 250, 255); + SETCOL(btheme->tfile.text_hi, 15, 15, 15, 255); + SETCOL(btheme->tfile.panel, 180, 180, 180, 255); // bookmark/ui regions + SETCOL(btheme->tfile.active, 130, 130, 130, 255); // selected files + SETCOL(btheme->tfile.hilite, 255, 140, 25, 255); // selected files + + SETCOL(btheme->tfile.grid, 250, 250, 250, 255); + SETCOL(btheme->tfile.image, 250, 250, 250, 255); + SETCOL(btheme->tfile.movie, 250, 250, 250, 255); + SETCOL(btheme->tfile.scene, 250, 250, 250, 255); + + + + + /* space nla */ + btheme->tnla= btheme->tv3d; + SETCOL(btheme->tnla.back, 116, 116, 116, 255); + SETCOL(btheme->tnla.text, 0, 0, 0, 255); + SETCOL(btheme->tnla.text_hi, 255, 255, 255, 255); + SETCOL(btheme->tnla.grid, 94, 94, 94, 255); + SETCOL(btheme->tnla.shade1, 172, 172, 172, 255); // sliders + SETCOL(btheme->tnla.shade2, 84, 44, 31, 100); // bar + SETCOL(btheme->tnla.hilite, 17, 27, 60, 100); // bar + SETCOL(btheme->tnla.strip_select, 0xff, 0xff, 0xaa, 255); + SETCOL(btheme->tnla.strip, 0xe4, 0x9c, 0xc6, 255); + + /* space seq */ + btheme->tseq= btheme->tv3d; + SETCOL(btheme->tseq.back, 116, 116, 116, 255); + SETCOL(btheme->tseq.movie, 81, 105, 135, 255); + SETCOL(btheme->tseq.image, 109, 88, 129, 255); + SETCOL(btheme->tseq.scene, 78, 152, 62, 255); + SETCOL(btheme->tseq.audio, 46, 143, 143, 255); + SETCOL(btheme->tseq.effect, 169, 84, 124, 255); + SETCOL(btheme->tseq.plugin, 126, 126, 80, 255); + SETCOL(btheme->tseq.transition, 162, 95, 111, 255); + SETCOL(btheme->tseq.meta, 109, 145, 131, 255); + + + /* space image */ + btheme->tima= btheme->tv3d; + SETCOL(btheme->tima.back, 53, 53, 53, 255); + SETCOL(btheme->tima.vertex, 0, 0, 0, 255); + SETCOL(btheme->tima.vertex_select, 255, 133, 0, 255); + btheme->tima.vertex_size= 3; + btheme->tima.facedot_size= 3; + SETCOL(btheme->tima.face, 255, 255, 255, 10); + SETCOL(btheme->tima.face_select, 255, 133, 0, 60); + SETCOL(btheme->tima.editmesh_active, 255, 255, 255, 128); + + /* space imageselect */ + btheme->timasel= btheme->tv3d; + SETCOL(btheme->timasel.active, 195, 195, 195, 255); /* active tile */ + SETCOL(btheme->timasel.grid, 94, 94, 94, 255); /* active file text */ + SETCOL(btheme->timasel.back, 110, 110, 110, 255); + SETCOL(btheme->timasel.shade1, 94, 94, 94, 255); /* bar */ + SETCOL(btheme->timasel.shade2, 172, 172, 172, 255); /* sliders */ + SETCOL(btheme->timasel.hilite, 17, 27, 60, 100); /* selected tile */ + SETCOL(btheme->timasel.text, 0, 0, 0, 255); + SETCOL(btheme->timasel.text_hi, 255, 255, 255, 255); + SETCOL(btheme->timasel.panel, 132, 132, 132, 255); + + /* space text */ + btheme->text= btheme->tv3d; + SETCOL(btheme->text.back, 153, 153, 153, 255); + SETCOL(btheme->text.shade1, 143, 143, 143, 255); + SETCOL(btheme->text.shade2, 0xc6, 0x77, 0x77, 255); + SETCOL(btheme->text.hilite, 255, 0, 0, 255); + + /* syntax highlighting */ + SETCOL(btheme->text.syntaxn, 0, 0, 200, 255); /* Numbers Blue*/ + SETCOL(btheme->text.syntaxl, 100, 0, 0, 255); /* Strings red */ + SETCOL(btheme->text.syntaxc, 0, 100, 50, 255); /* Comments greenish */ + SETCOL(btheme->text.syntaxv, 95, 95, 0, 255); /* Special */ + SETCOL(btheme->text.syntaxb, 128, 0, 80, 255); /* Builtin, red-purple */ + + /* space oops */ + btheme->toops= btheme->tv3d; + SETCOLF(btheme->toops.back, 0.45, 0.45, 0.45, 1.0); + + /* space info */ + btheme->tinfo= btheme->tv3d; + SETCOL(btheme->tinfo.back, 153, 153, 153, 255); + + /* space sound */ + btheme->tsnd= btheme->tv3d; + SETCOLF(btheme->tsnd.back, 0.45, 0.45, 0.45, 1.0); + SETCOLF(btheme->tsnd.grid, 0.36, 0.36, 0.36, 1.0); + SETCOL(btheme->tsnd.shade1, 173, 173, 173, 255); // sliders + + /* space time */ + btheme->ttime= btheme->tsnd; // same as sound space + + /* space node, re-uses syntax color storage */ + btheme->tnode= btheme->tv3d; + SETCOL(btheme->tnode.edge_select, 255, 255, 255, 255); + SETCOL(btheme->tnode.syntaxl, 150, 150, 150, 255); /* TH_NODE, backdrop */ + SETCOL(btheme->tnode.syntaxn, 129, 131, 144, 255); /* in/output */ + SETCOL(btheme->tnode.syntaxb, 127,127,127, 255); /* operator */ + SETCOL(btheme->tnode.syntaxv, 142, 138, 145, 255); /* generator */ + SETCOL(btheme->tnode.syntaxc, 120, 145, 120, 255); /* group */ + +} + + +void UI_SetTheme(int spacetype, int regionid) +{ + if(spacetype==0) { // called for safety, when delete themes + theme_active= U.themes.first; + theme_spacetype= SPACE_VIEW3D; + theme_regionid= RGN_TYPE_WINDOW; + } + else { + // later on, a local theme can be found too + theme_active= U.themes.first; + theme_spacetype= spacetype; + theme_regionid= regionid; + } +} + +// for space windows only +void UI_ThemeColor(int colorid) +{ + char *cp; + + cp= UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + glColor3ub(cp[0], cp[1], cp[2]); + +} + +// plus alpha +void UI_ThemeColor4(int colorid) +{ + char *cp; + + cp= UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + glColor4ub(cp[0], cp[1], cp[2], cp[3]); + +} + +// set the color with offset for shades +void UI_ThemeColorShade(int colorid, int offset) +{ + int r, g, b; + char *cp; + + cp= UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + r= offset + (int) cp[0]; + CLAMP(r, 0, 255); + g= offset + (int) cp[1]; + CLAMP(g, 0, 255); + b= offset + (int) cp[2]; + CLAMP(b, 0, 255); + //glColor3ub(r, g, b); + glColor4ub(r, g, b, cp[3]); +} +void UI_ThemeColorShadeAlpha(int colorid, int coloffset, int alphaoffset) +{ + int r, g, b, a; + char *cp; + + cp= UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + r= coloffset + (int) cp[0]; + CLAMP(r, 0, 255); + g= coloffset + (int) cp[1]; + CLAMP(g, 0, 255); + b= coloffset + (int) cp[2]; + CLAMP(b, 0, 255); + a= alphaoffset + (int) cp[3]; + CLAMP(a, 0, 255); + glColor4ub(r, g, b, a); +} + +// blend between to theme colors, and set it +void UI_ThemeColorBlend(int colorid1, int colorid2, float fac) +{ + int r, g, b; + char *cp1, *cp2; + + cp1= UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid1); + cp2= UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid2); + + if(fac<0.0) fac=0.0; else if(fac>1.0) fac= 1.0; + r= floor((1.0-fac)*cp1[0] + fac*cp2[0]); + g= floor((1.0-fac)*cp1[1] + fac*cp2[1]); + b= floor((1.0-fac)*cp1[2] + fac*cp2[2]); + + glColor3ub(r, g, b); +} + +// blend between to theme colors, shade it, and set it +void UI_ThemeColorBlendShade(int colorid1, int colorid2, float fac, int offset) +{ + int r, g, b; + char *cp1, *cp2; + + cp1= UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid1); + cp2= UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid2); + + if(fac<0.0) fac=0.0; else if(fac>1.0) fac= 1.0; + r= offset+floor((1.0-fac)*cp1[0] + fac*cp2[0]); + g= offset+floor((1.0-fac)*cp1[1] + fac*cp2[1]); + b= offset+floor((1.0-fac)*cp1[2] + fac*cp2[2]); + + CLAMP(r, 0, 255); + CLAMP(g, 0, 255); + CLAMP(b, 0, 255); + + glColor3ub(r, g, b); +} + +// blend between to theme colors, shade it, and set it +void UI_ThemeColorBlendShadeAlpha(int colorid1, int colorid2, float fac, int offset, int alphaoffset) +{ + int r, g, b, a; + char *cp1, *cp2; + + cp1= UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid1); + cp2= UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid2); + + if(fac<0.0) fac=0.0; else if(fac>1.0) fac= 1.0; + r= offset+floor((1.0-fac)*cp1[0] + fac*cp2[0]); + g= offset+floor((1.0-fac)*cp1[1] + fac*cp2[1]); + b= offset+floor((1.0-fac)*cp1[2] + fac*cp2[2]); + a= alphaoffset+floor((1.0-fac)*cp1[3] + fac*cp2[3]); + + CLAMP(r, 0, 255); + CLAMP(g, 0, 255); + CLAMP(b, 0, 255); + CLAMP(a, 0, 255); + + glColor4ub(r, g, b, a); +} + + +// get individual values, not scaled +float UI_GetThemeValuef(int colorid) +{ + char *cp; + + cp= UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + return ((float)cp[0]); + +} + +// get individual values, not scaled +int UI_GetThemeValue(int colorid) +{ + char *cp; + + cp= UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + return ((int) cp[0]); + +} + + +// get the color, range 0.0-1.0 +void UI_GetThemeColor3fv(int colorid, float *col) +{ + char *cp; + + cp= UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + col[0]= ((float)cp[0])/255.0; + col[1]= ((float)cp[1])/255.0; + col[2]= ((float)cp[2])/255.0; +} + +// get the color, in char pointer +void UI_GetThemeColor3ubv(int colorid, char *col) +{ + char *cp; + + cp= UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + col[0]= cp[0]; + col[1]= cp[1]; + col[2]= cp[2]; +} + +// get the color, in char pointer +void UI_GetThemeColor4ubv(int colorid, char *col) +{ + char *cp; + + cp= UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + col[0]= cp[0]; + col[1]= cp[1]; + col[2]= cp[2]; + col[3]= cp[3]; +} + +void UI_GetThemeColorType4ubv(int colorid, int spacetype, char *col) +{ + char *cp; + + cp= UI_ThemeGetColorPtr(theme_active, spacetype, colorid); + col[0]= cp[0]; + col[1]= cp[1]; + col[2]= cp[2]; + col[3]= cp[3]; +} + +// blends and shades between two char color pointers +void UI_ColorPtrBlendShade3ubv(char *cp1, char *cp2, float fac, int offset) +{ + int r, g, b; + + if(fac<0.0) fac=0.0; else if(fac>1.0) fac= 1.0; + r= offset+floor((1.0-fac)*cp1[0] + fac*cp2[0]); + g= offset+floor((1.0-fac)*cp1[1] + fac*cp2[1]); + b= offset+floor((1.0-fac)*cp1[2] + fac*cp2[2]); + + r= r<0?0:(r>255?255:r); + g= g<0?0:(g>255?255:g); + b= b<0?0:(b>255?255:b); + + glColor3ub(r, g, b); +} + +// get a 3 byte color, blended and shaded between two other char color pointers +void UI_GetColorPtrBlendShade3ubv(char *cp1, char *cp2, char *col, float fac, int offset) +{ + int r, g, b; + + if(fac<0.0) fac=0.0; else if(fac>1.0) fac= 1.0; + r= offset+floor((1.0-fac)*cp1[0] + fac*cp2[0]); + g= offset+floor((1.0-fac)*cp1[1] + fac*cp2[1]); + b= offset+floor((1.0-fac)*cp1[2] + fac*cp2[2]); + + r= r<0?0:(r>255?255:r); + g= g<0?0:(g>255?255:g); + b= b<0?0:(b>255?255:b); + + col[0] = r; + col[1] = g; + col[2] = b; +} + +void UI_make_axis_color(char *src_col, char *dst_col, char axis) +{ + switch(axis) + { + case 'x': + case 'X': + dst_col[0]= src_col[0]>219?255:src_col[0]+36; + dst_col[1]= src_col[1]<26?0:src_col[1]-26; + dst_col[2]= src_col[2]<26?0:src_col[2]-26; + break; + case 'y': + case 'Y': + dst_col[0]= src_col[0]<46?0:src_col[0]-36; + dst_col[1]= src_col[1]>189?255:src_col[1]+66; + dst_col[2]= src_col[2]<46?0:src_col[2]-36; + break; + default: + dst_col[0]= src_col[0]<26?0:src_col[0]-26; + dst_col[1]= src_col[1]<26?0:src_col[1]-26; + dst_col[2]= src_col[2]>209?255:src_col[2]+46; + } +} + +/* ************************************************************* */ + +/* patching UserDef struct and Themes */ +void init_userdef_do_versions(void) +{ +// countall(); + + /* the UserDef struct is not corrected with do_versions() .... ugh! */ + if(U.wheellinescroll == 0) U.wheellinescroll = 3; + if(U.menuthreshold1==0) { + U.menuthreshold1= 5; + U.menuthreshold2= 2; + } + if(U.tb_leftmouse==0) { + U.tb_leftmouse= 5; + U.tb_rightmouse= 5; + } + if(U.mixbufsize==0) U.mixbufsize= 2048; + if (BLI_streq(U.tempdir, "/")) { + char *tmp= getenv("TEMP"); + + strcpy(U.tempdir, tmp?tmp:"/tmp/"); + } + if (U.savetime <= 0) { + U.savetime = 1; +// XXX error(".B.blend is buggy, please consider removing it.\n"); + } + /* transform widget settings */ + if(U.tw_hotspot==0) { + U.tw_hotspot= 14; + U.tw_size= 20; // percentage of window size + U.tw_handlesize= 16; // percentage of widget radius + } + if(U.pad_rot_angle==0) + U.pad_rot_angle= 15; + + if(U.flag & USER_CUSTOM_RANGE) + vDM_ColorBand_store(&U.coba_weight); /* signal for derivedmesh to use colorband */ + + if (G.main->versionfile <= 191) { + strcpy(U.plugtexdir, U.textudir); + strcpy(U.sounddir, "/"); + } + + /* patch to set Dupli Armature */ + if (G.main->versionfile < 220) { + U.dupflag |= USER_DUP_ARM; + } + + /* added seam, normal color, undo */ + if (G.main->versionfile <= 234) { + bTheme *btheme; + + U.uiflag |= USER_GLOBALUNDO; + if (U.undosteps==0) U.undosteps=32; + + for(btheme= U.themes.first; btheme; btheme= btheme->next) { + /* check for alpha==0 is safe, then color was never set */ + if(btheme->tv3d.edge_seam[3]==0) { + SETCOL(btheme->tv3d.edge_seam, 230, 150, 50, 255); + } + if(btheme->tv3d.normal[3]==0) { + SETCOL(btheme->tv3d.normal, 0x22, 0xDD, 0xDD, 255); + } + if(btheme->tv3d.face_dot[3]==0) { + SETCOL(btheme->tv3d.face_dot, 255, 138, 48, 255); + btheme->tv3d.facedot_size= 4; + } + } + } + if (G.main->versionfile <= 235) { + /* illegal combo... */ + if (U.flag & USER_LMOUSESELECT) + U.flag &= ~USER_TWOBUTTONMOUSE; + } + if (G.main->versionfile <= 236) { + bTheme *btheme; + /* new space type */ + for(btheme= U.themes.first; btheme; btheme= btheme->next) { + /* check for alpha==0 is safe, then color was never set */ + if(btheme->ttime.back[3]==0) { + btheme->ttime = btheme->tsnd; // copy from sound + } + if(btheme->text.syntaxn[3]==0) { + SETCOL(btheme->text.syntaxn, 0, 0, 200, 255); /* Numbers Blue*/ + SETCOL(btheme->text.syntaxl, 100, 0, 0, 255); /* Strings red */ + SETCOL(btheme->text.syntaxc, 0, 100, 50, 255); /* Comments greenish */ + SETCOL(btheme->text.syntaxv, 95, 95, 0, 255); /* Special */ + SETCOL(btheme->text.syntaxb, 128, 0, 80, 255); /* Builtin, red-purple */ + } + } + } + if (G.main->versionfile <= 237) { + bTheme *btheme; + /* bone colors */ + for(btheme= U.themes.first; btheme; btheme= btheme->next) { + /* check for alpha==0 is safe, then color was never set */ + if(btheme->tv3d.bone_solid[3]==0) { + SETCOL(btheme->tv3d.bone_solid, 200, 200, 200, 255); + SETCOL(btheme->tv3d.bone_pose, 80, 200, 255, 80); + } + } + } + if (G.main->versionfile <= 238) { + bTheme *btheme; + /* bone colors */ + for(btheme= U.themes.first; btheme; btheme= btheme->next) { + /* check for alpha==0 is safe, then color was never set */ + if(btheme->tnla.strip[3]==0) { + SETCOL(btheme->tnla.strip_select, 0xff, 0xff, 0xaa, 255); + SETCOL(btheme->tnla.strip, 0xe4, 0x9c, 0xc6, 255); + } + } + } + if (G.main->versionfile <= 239) { + bTheme *btheme; + + for(btheme= U.themes.first; btheme; btheme= btheme->next) { + /* Lamp theme, check for alpha==0 is safe, then color was never set */ + if(btheme->tv3d.lamp[3]==0) { + SETCOL(btheme->tv3d.lamp, 0, 0, 0, 40); +/* TEMPORAL, remove me! (ton) */ + U.uiflag |= USER_PLAINMENUS; + } + + } + if(U.obcenter_dia==0) U.obcenter_dia= 6; + } + if (G.main->versionfile <= 241) { + bTheme *btheme; + for(btheme= U.themes.first; btheme; btheme= btheme->next) { + /* Node editor theme, check for alpha==0 is safe, then color was never set */ + if(btheme->tnode.syntaxn[3]==0) { + /* re-uses syntax color storage */ + btheme->tnode= btheme->tv3d; + SETCOL(btheme->tnode.edge_select, 255, 255, 255, 255); + SETCOL(btheme->tnode.syntaxl, 150, 150, 150, 255); /* TH_NODE, backdrop */ + SETCOL(btheme->tnode.syntaxn, 129, 131, 144, 255); /* in/output */ + SETCOL(btheme->tnode.syntaxb, 127,127,127, 255); /* operator */ + SETCOL(btheme->tnode.syntaxv, 142, 138, 145, 255); /* generator */ + SETCOL(btheme->tnode.syntaxc, 120, 145, 120, 255); /* group */ + } + /* Group theme colors */ + if(btheme->tv3d.group[3]==0) { + SETCOL(btheme->tv3d.group, 0x10, 0x40, 0x10, 255); + SETCOL(btheme->tv3d.group_active, 0x66, 0xFF, 0x66, 255); + } + /* Sequence editor theme*/ + if(btheme->tseq.movie[3]==0) { + SETCOL(btheme->tseq.movie, 81, 105, 135, 255); + SETCOL(btheme->tseq.image, 109, 88, 129, 255); + SETCOL(btheme->tseq.scene, 78, 152, 62, 255); + SETCOL(btheme->tseq.audio, 46, 143, 143, 255); + SETCOL(btheme->tseq.effect, 169, 84, 124, 255); + SETCOL(btheme->tseq.plugin, 126, 126, 80, 255); + SETCOL(btheme->tseq.transition, 162, 95, 111, 255); + SETCOL(btheme->tseq.meta, 109, 145, 131, 255); + } + } + + /* set defaults for 3D View rotating axis indicator */ + /* since size can't be set to 0, this indicates it's not saved in .B.blend */ + if (U.rvisize == 0) { + U.rvisize = 15; + U.rvibright = 8; + U.uiflag |= USER_SHOW_ROTVIEWICON; + } + + } + if (G.main->versionfile <= 242) { + bTheme *btheme; + + for(btheme= U.themes.first; btheme; btheme= btheme->next) { + /* long keyframe color */ + /* check for alpha==0 is safe, then color was never set */ + if(btheme->tact.strip[3]==0) { + SETCOL(btheme->tv3d.edge_sharp, 255, 32, 32, 255); + SETCOL(btheme->tact.strip_select, 0xff, 0xff, 0xaa, 204); + SETCOL(btheme->tact.strip, 0xe4, 0x9c, 0xc6, 204); + } + + /* IPO-Editor - Vertex Size*/ + if(btheme->tipo.vertex_size == 0) { + btheme->tipo.vertex_size= 3; + } + } + } + if (G.main->versionfile <= 243) { + /* set default number of recently-used files (if not set) */ + if (U.recent_files == 0) U.recent_files = 10; + } + if (G.main->versionfile < 245 || (G.main->versionfile == 245 && G.main->subversionfile < 3)) { + bTheme *btheme; + for(btheme= U.themes.first; btheme; btheme= btheme->next) { + SETCOL(btheme->tv3d.editmesh_active, 255, 255, 255, 128); + } + if(U.coba_weight.tot==0) + init_colorband(&U.coba_weight, 1); + } + if ((G.main->versionfile < 245) || (G.main->versionfile == 245 && G.main->subversionfile < 11)) { + bTheme *btheme; + for (btheme= U.themes.first; btheme; btheme= btheme->next) { + /* these should all use the same colour */ + SETCOL(btheme->tv3d.cframe, 0x60, 0xc0, 0x40, 255); + SETCOL(btheme->tipo.cframe, 0x60, 0xc0, 0x40, 255); + SETCOL(btheme->tact.cframe, 0x60, 0xc0, 0x40, 255); + SETCOL(btheme->tnla.cframe, 0x60, 0xc0, 0x40, 255); + SETCOL(btheme->tseq.cframe, 0x60, 0xc0, 0x40, 255); + SETCOL(btheme->tsnd.cframe, 0x60, 0xc0, 0x40, 255); + SETCOL(btheme->ttime.cframe, 0x60, 0xc0, 0x40, 255); + } + } + if ((G.main->versionfile < 245) || (G.main->versionfile == 245 && G.main->subversionfile < 11)) { + bTheme *btheme; + for (btheme= U.themes.first; btheme; btheme= btheme->next) { + /* these should all use the same color */ + SETCOL(btheme->tv3d.cframe, 0x60, 0xc0, 0x40, 255); + SETCOL(btheme->tipo.cframe, 0x60, 0xc0, 0x40, 255); + SETCOL(btheme->tact.cframe, 0x60, 0xc0, 0x40, 255); + SETCOL(btheme->tnla.cframe, 0x60, 0xc0, 0x40, 255); + SETCOL(btheme->tseq.cframe, 0x60, 0xc0, 0x40, 255); + SETCOL(btheme->tsnd.cframe, 0x60, 0xc0, 0x40, 255); + SETCOL(btheme->ttime.cframe, 0x60, 0xc0, 0x40, 255); + } + } + if ((G.main->versionfile < 245) || (G.main->versionfile == 245 && G.main->subversionfile < 13)) { + bTheme *btheme; + for (btheme= U.themes.first; btheme; btheme= btheme->next) { + /* action channel groups (recolor anyway) */ + SETCOL(btheme->tact.group, 0x39, 0x7d, 0x1b, 255); + SETCOL(btheme->tact.group_active, 0x7d, 0xe9, 0x60, 255); + + /* bone custom-color sets */ + // FIXME: this check for initialised colors is bad + if (btheme->tarm[0].solid[3] == 0) { + /* set 1 */ + SETCOL(btheme->tarm[0].solid, 0x9a, 0x00, 0x00, 255); + SETCOL(btheme->tarm[0].select, 0xbd, 0x11, 0x11, 255); + SETCOL(btheme->tarm[0].active, 0xf7, 0x0a, 0x0a, 255); + /* set 2 */ + SETCOL(btheme->tarm[1].solid, 0xf7, 0x40, 0x18, 255); + SETCOL(btheme->tarm[1].select, 0xf6, 0x69, 0x13, 255); + SETCOL(btheme->tarm[1].active, 0xfa, 0x99, 0x00, 255); + /* set 3 */ + SETCOL(btheme->tarm[2].solid, 0x1e, 0x91, 0x09, 255); + SETCOL(btheme->tarm[2].select, 0x59, 0xb7, 0x0b, 255); + SETCOL(btheme->tarm[2].active, 0x83, 0xef, 0x1d, 255); + /* set 4 */ + SETCOL(btheme->tarm[3].solid, 0x0a, 0x36, 0x94, 255); + SETCOL(btheme->tarm[3].select, 0x36, 0x67, 0xdf, 255); + SETCOL(btheme->tarm[3].active, 0x5e, 0xc1, 0xef, 255); + /* set 5 */ + SETCOL(btheme->tarm[4].solid, 0xa9, 0x29, 0x4e, 255); + SETCOL(btheme->tarm[4].select, 0xc1, 0x41, 0x6a, 255); + SETCOL(btheme->tarm[4].active, 0xf0, 0x5d, 0x91, 255); + /* set 6 */ + SETCOL(btheme->tarm[5].solid, 0x43, 0x0c, 0x78, 255); + SETCOL(btheme->tarm[5].select, 0x54, 0x3a, 0xa3, 255); + SETCOL(btheme->tarm[5].active, 0x87, 0x64, 0xd5, 255); + /* set 7 */ + SETCOL(btheme->tarm[6].solid, 0x24, 0x78, 0x5a, 255); + SETCOL(btheme->tarm[6].select, 0x3c, 0x95, 0x79, 255); + SETCOL(btheme->tarm[6].active, 0x6f, 0xb6, 0xab, 255); + /* set 8 */ + SETCOL(btheme->tarm[7].solid, 0x4b, 0x70, 0x7c, 255); + SETCOL(btheme->tarm[7].select, 0x6a, 0x86, 0x91, 255); + SETCOL(btheme->tarm[7].active, 0x9b, 0xc2, 0xcd, 255); + /* set 9 */ + SETCOL(btheme->tarm[8].solid, 0xf4, 0xc9, 0x0c, 255); + SETCOL(btheme->tarm[8].select, 0xee, 0xc2, 0x36, 255); + SETCOL(btheme->tarm[8].active, 0xf3, 0xff, 0x00, 255); + /* set 10 */ + SETCOL(btheme->tarm[9].solid, 0x1e, 0x20, 0x24, 255); + SETCOL(btheme->tarm[9].select, 0x48, 0x4c, 0x56, 255); + SETCOL(btheme->tarm[9].active, 0xff, 0xff, 0xff, 255); + /* set 11 */ + SETCOL(btheme->tarm[10].solid, 0x6f, 0x2f, 0x6a, 255); + SETCOL(btheme->tarm[10].select, 0x98, 0x45, 0xbe, 255); + SETCOL(btheme->tarm[10].active, 0xd3, 0x30, 0xd6, 255); + /* set 12 */ + SETCOL(btheme->tarm[11].solid, 0x6c, 0x8e, 0x22, 255); + SETCOL(btheme->tarm[11].select, 0x7f, 0xb0, 0x22, 255); + SETCOL(btheme->tarm[11].active, 0xbb, 0xef, 0x5b, 255); + /* set 13 */ + SETCOL(btheme->tarm[12].solid, 0x8d, 0x8d, 0x8d, 255); + SETCOL(btheme->tarm[12].select, 0xb0, 0xb0, 0xb0, 255); + SETCOL(btheme->tarm[12].active, 0xde, 0xde, 0xde, 255); + /* set 14 */ + SETCOL(btheme->tarm[13].solid, 0x83, 0x43, 0x26, 255); + SETCOL(btheme->tarm[13].select, 0x8b, 0x58, 0x11, 255); + SETCOL(btheme->tarm[13].active, 0xbd, 0x6a, 0x11, 255); + /* set 15 */ + SETCOL(btheme->tarm[14].solid, 0x08, 0x31, 0x0e, 255); + SETCOL(btheme->tarm[14].select, 0x1c, 0x43, 0x0b, 255); + SETCOL(btheme->tarm[14].active, 0x34, 0x62, 0x2b, 255); + } + } + } + if ((G.main->versionfile < 245) || (G.main->versionfile == 245 && G.main->subversionfile < 16)) { + U.flag |= USER_ADD_VIEWALIGNED|USER_ADD_EDITMODE; + } + if ((G.main->versionfile < 247) || (G.main->versionfile == 247 && G.main->subversionfile <= 2)) { + bTheme *btheme; + + /* adjust themes */ + for (btheme= U.themes.first; btheme; btheme= btheme->next) { + char *col; + + /* IPO Editor: Handles/Vertices */ + col = btheme->tipo.vertex; + SETCOL(btheme->tipo.handle_vertex, col[0], col[1], col[2], 255); + col = btheme->tipo.vertex_select; + SETCOL(btheme->tipo.handle_vertex_select, col[0], col[1], col[2], 255); + btheme->tipo.handle_vertex_size= btheme->tipo.vertex_size; + + /* Sequence/Image Editor: colors for GPencil text */ + col = btheme->tv3d.bone_pose; + SETCOL(btheme->tseq.bone_pose, col[0], col[1], col[2], 255); + SETCOL(btheme->tima.bone_pose, col[0], col[1], col[2], 255); + col = btheme->tv3d.vertex_select; + SETCOL(btheme->tseq.vertex_select, col[0], col[1], col[2], 255); + } + } + if (G.main->versionfile < 250) { + bTheme *btheme; + + for(btheme= U.themes.first; btheme; btheme= btheme->next) { + /* this was not properly initialized in 2.45 */ + if(btheme->tima.face_dot[3]==0) { + SETCOL(btheme->tima.editmesh_active, 255, 255, 255, 128); + SETCOL(btheme->tima.face_dot, 255, 133, 0, 255); + btheme->tima.facedot_size= 2; + } + + /* DopeSheet - (Object) Channel color */ + SETCOL(btheme->tact.ds_channel, 82, 96, 110, 255); + SETCOL(btheme->tact.ds_subchannel, 124, 137, 150, 255); + /* DopeSheet - Group Channel color (saner version) */ + SETCOL(btheme->tact.group, 79, 101, 73, 255); + SETCOL(btheme->tact.group_active, 135, 177, 125, 255); + + /* Graph Editor - (Object) Channel color */ + SETCOL(btheme->tipo.ds_channel, 82, 96, 110, 255); + SETCOL(btheme->tipo.ds_subchannel, 124, 137, 150, 255); + /* Graph Editor - Group Channel color */ + SETCOL(btheme->tipo.group, 79, 101, 73, 255); + SETCOL(btheme->tipo.group_active, 135, 177, 125, 255); + } + + /* adjust grease-pencil distances */ + U.gp_manhattendist= 1; + U.gp_euclideandist= 2; + + /* adjust default interpolation for new IPO-curves */ + U.ipo_new= BEZT_IPO_BEZ; + } + + if (G.main->versionfile < 250 || (G.main->versionfile == 250 && G.main->subversionfile < 1)) { + bTheme *btheme; + + for(btheme= U.themes.first; btheme; btheme= btheme->next) { + + /* common (new) variables, it checks for alpha==0 */ + ui_theme_init_new(btheme); + + if(btheme->tui.wcol_num.outline[3]==0) + ui_widget_color_init(&btheme->tui); + } + } + + /* GL Texture Garbage Collection (variable abused above!) */ + if (U.textimeout == 0) { + U.texcollectrate = 60; + U.textimeout = 120; + } + if (U.memcachelimit <= 0) { + U.memcachelimit = 32; + } + if (U.frameserverport == 0) { + U.frameserverport = 8080; + } + + /* funny name, but it is GE stuff, moves userdef stuff to engine */ +// XXX space_set_commmandline_options(); + /* this timer uses U */ +// XXX reset_autosave(); + +} + + + diff --git a/source/blender/editors/interface/view2d.c b/source/blender/editors/interface/view2d.c new file mode 100644 index 00000000000..b363f1f6272 --- /dev/null +++ b/source/blender/editors/interface/view2d.c @@ -0,0 +1,2023 @@ +/** + * $Id$ + * + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2008 Blender Foundation. + * All rights reserved. + * + * Contributor(s): Blender Foundation, Joshua Leung + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include <limits.h> +#include <math.h> +#include <string.h> + +#include "MEM_guardedalloc.h" + +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" +#include "DNA_view2d_types.h" + +#include "BLI_blenlib.h" + +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_utildefines.h" + +#include "WM_api.h" + +#include "BIF_gl.h" +#include "BIF_glutil.h" + +#include "BLF_api.h" + +#include "ED_screen.h" + +#include "UI_interface.h" +#include "UI_resources.h" +#include "UI_view2d.h" + +#include "interface_intern.h" + +/* *********************************************************************** */ + +/* helper to allow scrollbars to dynamically hide */ +static int view2d_scroll_mapped(int scroll) +{ + if(scroll & V2D_SCROLL_HORIZONTAL_HIDE) + scroll &= ~(V2D_SCROLL_HORIZONTAL); + if(scroll & V2D_SCROLL_VERTICAL_HIDE) + scroll &= ~(V2D_SCROLL_VERTICAL); + return scroll; +} + +/* called each time cur changes, to dynamically update masks */ +static void view2d_masks(View2D *v2d) +{ + int scroll; + + /* mask - view frame */ + v2d->mask.xmin= v2d->mask.ymin= 0; + v2d->mask.xmax= v2d->winx - 1; /* -1 yes! masks are pixels */ + v2d->mask.ymax= v2d->winy - 1; + +#if 0 + v2d->scroll &= ~(V2D_SCROLL_HORIZONTAL_HIDE|V2D_SCROLL_VERTICAL_HIDE); + /* check size if: */ + if (v2d->scroll & V2D_SCROLL_HORIZONTAL) + if(!(v2d->scroll & V2D_SCROLL_SCALE_HORIZONTAL)) + if (v2d->tot.xmax-v2d->tot.xmin <= v2d->cur.xmax-v2d->cur.xmin) + v2d->scroll |= V2D_SCROLL_HORIZONTAL_HIDE; + if (v2d->scroll & V2D_SCROLL_VERTICAL) + if(!(v2d->scroll & V2D_SCROLL_SCALE_VERTICAL)) + if (v2d->tot.ymax-v2d->tot.ymin <= v2d->cur.ymax-v2d->cur.ymin) + v2d->scroll |= V2D_SCROLL_VERTICAL_HIDE; +#endif + scroll= view2d_scroll_mapped(v2d->scroll); + + /* scrollers shrink mask area, but should be based off regionsize + * - they can only be on one to two edges of the region they define + * - if they overlap, they must not occupy the corners (which are reserved for other widgets) + */ + if (scroll) { + /* vertical scroller */ + if (scroll & V2D_SCROLL_LEFT) { + /* on left-hand edge of region */ + v2d->vert= v2d->mask; + v2d->vert.xmax= V2D_SCROLL_WIDTH; + v2d->mask.xmin= v2d->vert.xmax + 1; + } + else if (scroll & V2D_SCROLL_RIGHT) { + /* on right-hand edge of region */ + v2d->vert= v2d->mask; + v2d->vert.xmax++; /* one pixel extra... was leaving a minor gap... */ + v2d->vert.xmin= v2d->vert.xmax - V2D_SCROLL_WIDTH; + v2d->mask.xmax= v2d->vert.xmin - 1; + } + + /* horizontal scroller */ + if (scroll & (V2D_SCROLL_BOTTOM|V2D_SCROLL_BOTTOM_O)) { + /* on bottom edge of region (V2D_SCROLL_BOTTOM_O is outliner, the other is for standard) */ + v2d->hor= v2d->mask; + v2d->hor.ymax= V2D_SCROLL_HEIGHT; + v2d->mask.ymin= v2d->hor.ymax + 1; + } + else if (scroll & V2D_SCROLL_TOP) { + /* on upper edge of region */ + v2d->hor= v2d->mask; + v2d->hor.ymin= v2d->hor.ymax - V2D_SCROLL_HEIGHT; + v2d->mask.ymax= v2d->hor.ymin - 1; + } + + /* adjust vertical scroller if there's a horizontal scroller, to leave corner free */ + if (scroll & V2D_SCROLL_VERTICAL) { + /* just set y min/max for vertical scroller to y min/max of mask as appropriate */ + if (scroll & (V2D_SCROLL_BOTTOM|V2D_SCROLL_BOTTOM_O)) { + /* on bottom edge of region (V2D_SCROLL_BOTTOM_O is outliner, the other is for standard) */ + v2d->vert.ymin= v2d->mask.ymin; + } + else if (scroll & V2D_SCROLL_TOP) { + /* on upper edge of region */ + v2d->vert.ymax= v2d->mask.ymax; + } + } + } + +} + +/* Refresh and Validation */ + +/* Initialise all relevant View2D data (including view rects if first time) and/or refresh mask sizes after view resize + * - for some of these presets, it is expected that the region will have defined some + * additional settings necessary for the customisation of the 2D viewport to its requirements + * - this function should only be called from region init() callbacks, where it is expected that + * this is called before UI_view2d_size_update(), as this one checks that the rects are properly initialised. + */ +void UI_view2d_region_reinit(View2D *v2d, short type, int winx, int winy) +{ + short tot_changed= 0; + + /* initialise data if there is a need for such */ + if ((v2d->flag & V2D_IS_INITIALISED) == 0) { + /* set initialised flag so that View2D doesn't get reinitialised next time again */ + v2d->flag |= V2D_IS_INITIALISED; + + /* see eView2D_CommonViewTypes in UI_view2d.h for available view presets */ + switch (type) { + /* 'standard view' - optimum setup for 'standard' view behaviour, that should be used new views as basis for their + * own unique View2D settings, which should be used instead of this in most cases... + */ + case V2D_COMMONVIEW_STANDARD: + { + /* for now, aspect ratio should be maintained, and zoom is clamped within sane default limits */ + v2d->keepzoom= (V2D_KEEPASPECT|V2D_KEEPZOOM); + v2d->minzoom= 0.01f; + v2d->maxzoom= 1000.0f; + + /* tot rect and cur should be same size, and aligned using 'standard' OpenGL coordinates for now + * - region can resize 'tot' later to fit other data + * - keeptot is only within bounds, as strict locking is not that critical + * - view is aligned for (0,0) -> (winx-1, winy-1) setup + */ + v2d->align= (V2D_ALIGN_NO_NEG_X|V2D_ALIGN_NO_NEG_Y); + v2d->keeptot= V2D_KEEPTOT_BOUNDS; + + v2d->tot.xmin= v2d->tot.ymin= 0.0f; + v2d->tot.xmax= (float)(winx - 1); + v2d->tot.ymax= (float)(winy - 1); + + v2d->cur= v2d->tot; + + /* scrollers - should we have these by default? */ + // XXX for now, we don't override this, or set it either! + } + break; + + /* 'list/channel view' - zoom, aspect ratio, and alignment restrictions are set here */ + case V2D_COMMONVIEW_LIST: + { + /* zoom + aspect ratio are locked */ + v2d->keepzoom = (V2D_LOCKZOOM_X|V2D_LOCKZOOM_Y|V2D_KEEPZOOM|V2D_KEEPASPECT); + v2d->minzoom= v2d->maxzoom= 1.0f; + + /* tot rect has strictly regulated placement, and must only occur in +/- quadrant */ + v2d->align = (V2D_ALIGN_NO_NEG_X|V2D_ALIGN_NO_POS_Y); + v2d->keeptot = V2D_KEEPTOT_STRICT; + tot_changed= 1; + + /* scroller settings are currently not set here... that is left for regions... */ + } + break; + + /* 'header' regions - zoom, aspect ratio, alignment, and panning restrictions are set here */ + case V2D_COMMONVIEW_HEADER: + { + /* zoom + aspect ratio are locked */ + v2d->keepzoom = (V2D_LOCKZOOM_X|V2D_LOCKZOOM_Y|V2D_KEEPZOOM|V2D_KEEPASPECT); + v2d->minzoom= v2d->maxzoom= 1.0f; + v2d->min[0]= v2d->max[0]= (float)(winx-1); + v2d->min[1]= v2d->max[1]= (float)(winy-1); + + /* tot rect has strictly regulated placement, and must only occur in +/+ quadrant */ + v2d->align = (V2D_ALIGN_NO_NEG_X|V2D_ALIGN_NO_NEG_Y); + v2d->keeptot = V2D_KEEPTOT_STRICT; + tot_changed= 1; + + /* panning in y-axis is prohibited */ + v2d->keepofs= V2D_LOCKOFS_Y; + + /* absolutely no scrollers allowed */ + v2d->scroll= 0; + + /* pixel offsets need to be applied for smooth UI controls */ + v2d->flag |= (V2D_PIXELOFS_X|V2D_PIXELOFS_Y); + } + break; + + /* panels view, with horizontal/vertical align */ + case V2D_COMMONVIEW_PANELS_UI: + { + /* for now, aspect ratio should be maintained, and zoom is clamped within sane default limits */ + v2d->keepzoom= (V2D_KEEPASPECT|V2D_KEEPZOOM); + v2d->minzoom= 0.5f; + v2d->maxzoom= 2.0f; + + v2d->align= (V2D_ALIGN_NO_NEG_X|V2D_ALIGN_NO_POS_Y); + v2d->keeptot= V2D_KEEPTOT_BOUNDS; + + v2d->tot.xmin= 0.0f; + v2d->tot.xmax= winx; + + v2d->tot.ymax= 0.0f; + v2d->tot.ymin= -winy; + + v2d->cur= v2d->tot; + } + break; + + /* other view types are completely defined using their own settings already */ + default: + /* we don't do anything here, as settings should be fine, but just make sure that rect */ + break; + } + } + + /* store view size */ + v2d->winx= winx; + v2d->winy= winy; + + /* set masks */ + view2d_masks(v2d); + + /* set 'tot' rect before setting cur? */ + if (tot_changed) + UI_view2d_totRect_set(v2d, winx, winy); + else + UI_view2d_curRect_validate(v2d); + +} + +/* Ensure View2D rects remain in a viable configuration + * - cur is not allowed to be: larger than max, smaller than min, or outside of tot + */ +// XXX pre2.5 -> this used to be called test_view2d() +void UI_view2d_curRect_validate(View2D *v2d) +{ + float totwidth, totheight, curwidth, curheight, width, height; + float winx, winy; + rctf *cur, *tot; + + /* use mask as size of region that View2D resides in, as it takes into account scrollbars already */ + winx= (float)(v2d->mask.xmax - v2d->mask.xmin + 1); + winy= (float)(v2d->mask.ymax - v2d->mask.ymin + 1); + + /* get pointers to rcts for less typing */ + cur= &v2d->cur; + tot= &v2d->tot; + + /* we must satisfy the following constraints (in decreasing order of importance): + * - alignment restrictions are respected + * - cur must not fall outside of tot + * - axis locks (zoom and offset) must be maintained + * - zoom must not be excessive (check either sizes or zoom values) + * - aspect ratio should be respected (NOTE: this is quite closely realted to zoom too) + */ + + /* Step 1: if keepzoom, adjust the sizes of the rects only + * - firstly, we calculate the sizes of the rects + * - curwidth and curheight are saved as reference... modify width and height values here + */ + totwidth= tot->xmax - tot->xmin; + totheight= tot->ymax - tot->ymin; + curwidth= width= cur->xmax - cur->xmin; + curheight= height= cur->ymax - cur->ymin; + + /* if zoom is locked, size on the appropriate axis is reset to mask size */ + if (v2d->keepzoom & V2D_LOCKZOOM_X) + width= winx; + if (v2d->keepzoom & V2D_LOCKZOOM_Y) + height= winy; + + /* keepzoom (V2D_KEEPZOOM set), indicates that zoom level on each axis must not exceed limits + * NOTE: in general, it is not expected that the lock-zoom will be used in conjunction with this + */ + if (v2d->keepzoom & V2D_KEEPZOOM) { + float zoom, fac; + + /* check if excessive zoom on x-axis */ + if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0) { + zoom= winx / width; + if ((zoom < v2d->minzoom) || (zoom > v2d->maxzoom)) { + fac= (zoom < v2d->minzoom) ? (zoom / v2d->minzoom) : (zoom / v2d->maxzoom); + width *= fac; + } + } + + /* check if excessive zoom on y-axis */ + if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0) { + zoom= winy / height; + if ((zoom < v2d->minzoom) || (zoom > v2d->maxzoom)) { + fac= (zoom < v2d->minzoom) ? (zoom / v2d->minzoom) : (zoom / v2d->maxzoom); + height *= fac; + } + } + } + else { + /* make sure sizes don't exceed that of the min/max sizes (even though we're not doing zoom clamping) */ + CLAMP(width, v2d->min[0], v2d->max[0]); + CLAMP(height, v2d->min[1], v2d->max[1]); + } + + /* check if we should restore aspect ratio (if view size changed) */ + if (v2d->keepzoom & V2D_KEEPASPECT) { + short do_x=0, do_y=0, do_cur, do_win; + float curRatio, winRatio; + + /* when a window edge changes, the aspect ratio can't be used to + * find which is the best new 'cur' rect. thats why it stores 'old' + */ + if (winx != v2d->oldwinx) do_x= 1; + if (winy != v2d->oldwiny) do_y= 1; + + curRatio= height / width; + winRatio= winy / winx; + + /* both sizes change (area/region maximised) */ + if (do_x == do_y) { + if (do_x && do_y) { + /* here is 1,1 case, so all others must be 0,0 */ + if (ABS(winx - v2d->oldwinx) > ABS(winy - v2d->oldwiny)) do_y= 0; + else do_x= 0; + } + else if (winRatio > 1.0f) do_x= 0; + else do_x= 1; + } + do_cur= do_x; + do_win= do_y; + + if (do_cur) { + if ((v2d->keeptot == V2D_KEEPTOT_STRICT) && (winx != v2d->oldwinx)) { + /* special exception for Outliner (and later channel-lists): + * - The view may be moved left to avoid contents being pushed out of view when view shrinks. + * - The keeptot code will make sure cur->xmin will not be less than tot->xmin (which cannot be allowed) + * - width is not adjusted for changed ratios here... + */ + if (winx < v2d->oldwinx) { + float temp = v2d->oldwinx - winx; + + cur->xmin -= temp; + cur->xmax -= temp; + + /* width does not get modified, as keepaspect here is just set to make + * sure visible area adjusts to changing view shape! + */ + } + } + else { + /* portrait window: correct for x */ + width= height / winRatio; + } + } + else { + if ((v2d->keeptot == V2D_KEEPTOT_STRICT) && (winy != v2d->oldwiny)) { + /* special exception for Outliner (and later channel-lists): + * - Currently, no actions need to be taken here... + */ + + if (winy < v2d->oldwiny) { + float temp = v2d->oldwiny - winy; + + cur->ymin += temp; + cur->ymax += temp; + } + + } + else { + /* landscape window: correct for y */ + height = width * winRatio; + } + } + + /* store region size for next time */ + v2d->oldwinx= (short)winx; + v2d->oldwiny= (short)winy; + } + + /* Step 2: apply new sizes to cur rect, but need to take into account alignment settings here... */ + if ((width != curwidth) || (height != curheight)) { + float temp, dh; + + /* resize from centerpoint */ + if (width != curwidth) { + if (v2d->keepofs & V2D_LOCKOFS_X) { + cur->xmax += width - (cur->xmax - cur->xmin); + } + else { + temp= (cur->xmax + cur->xmin) * 0.5f; + dh= width * 0.5f; + + cur->xmin = temp - dh; + cur->xmax = temp + dh; + } + } + if (height != curheight) { + if (v2d->keepofs & V2D_LOCKOFS_Y) { + cur->ymax += height - (cur->ymax - cur->ymin); + } + else { + temp= (cur->ymax + cur->ymin) * 0.5f; + dh= height * 0.5f; + + cur->ymin = temp - dh; + cur->ymax = temp + dh; + } + } + } + + /* Step 3: adjust so that it doesn't fall outside of bounds of 'tot' */ + if (v2d->keeptot) { + float temp, diff; + + /* recalculate extents of cur */ + curwidth= cur->xmax - cur->xmin; + curheight= cur->ymax - cur->ymin; + + /* width */ + if ( (curwidth > totwidth) && !(v2d->keepzoom & (V2D_KEEPZOOM|V2D_LOCKZOOM_X)) ) { + /* if zoom doesn't have to be maintained, just clamp edges */ + if (cur->xmin < tot->xmin) cur->xmin= tot->xmin; + if (cur->xmax > tot->xmax) cur->xmax= tot->xmax; + } + else if (v2d->keeptot == V2D_KEEPTOT_STRICT) { + /* This is an exception for the outliner (and later channel-lists, headers) + * - must clamp within tot rect (absolutely no excuses) + * --> therefore, cur->xmin must not be less than tot->xmin + */ + if (cur->xmin < tot->xmin) { + /* move cur across so that it sits at minimum of tot */ + temp= tot->xmin - cur->xmin; + + cur->xmin += temp; + cur->xmax += temp; + } + else if (cur->xmax > tot->xmax) { + /* - only offset by difference of cur-xmax and tot-xmax if that would not move + * cur-xmin to lie past tot-xmin + * - otherwise, simply shift to tot-xmin??? + */ + temp= cur->xmax - tot->xmax; + + if ((cur->xmin - temp) < tot->xmin) { + /* only offset by difference from cur-min and tot-min */ + temp= cur->xmin - tot->xmin; + + cur->xmin -= temp; + cur->xmax -= temp; + } + else { + cur->xmin -= temp; + cur->xmax -= temp; + } + } + } + else { + /* This here occurs when: + * - width too big, but maintaining zoom (i.e. widths cannot be changed) + * - width is OK, but need to check if outside of boundaries + * + * So, resolution is to just shift view by the gap between the extremities. + * We favour moving the 'minimum' across, as that's origin for most things + * (XXX - in the past, max was favoured... if there are bugs, swap!) + */ + if ((cur->ymin < tot->ymin) && (cur->ymax > tot->ymax)) { + /* outside boundaries on both sides, so take middle-point of tot, and place in balanced way */ + temp= (tot->ymax + tot->ymin) * 0.5f; + diff= curheight * 0.5f; + + cur->ymin= temp - diff; + cur->ymax= temp + diff; + } + else if (cur->xmin < tot->xmin) { + /* move cur across so that it sits at minimum of tot */ + temp= tot->xmin - cur->xmin; + + cur->xmin += temp; + cur->xmax += temp; + } + else if (cur->xmax > tot->xmax) { + /* - only offset by difference of cur-xmax and tot-xmax if that would not move + * cur-xmin to lie past tot-xmin + * - otherwise, simply shift to tot-xmin??? + */ + temp= cur->xmax - tot->xmax; + + if ((cur->xmin - temp) < tot->xmin) { + /* only offset by difference from cur-min and tot-min */ + temp= cur->xmin - tot->xmin; + + cur->xmin -= temp; + cur->xmax -= temp; + } + else { + cur->xmin -= temp; + cur->xmax -= temp; + } + } + } + + /* height */ + if ( (curheight > totheight) && !(v2d->keepzoom & (V2D_KEEPZOOM|V2D_LOCKZOOM_Y)) ) { + /* if zoom doesn't have to be maintained, just clamp edges */ + if (cur->ymin < tot->ymin) cur->ymin= tot->ymin; + if (cur->ymax > tot->ymax) cur->ymax= tot->ymax; + } + else { + /* This here occurs when: + * - height too big, but maintaining zoom (i.e. heights cannot be changed) + * - height is OK, but need to check if outside of boundaries + * + * So, resolution is to just shift view by the gap between the extremities. + * We favour moving the 'minimum' across, as that's origin for most things + */ + if ((cur->ymin < tot->ymin) && (cur->ymax > tot->ymax)) { + /* outside boundaries on both sides, so take middle-point of tot, and place in balanced way */ + temp= (tot->ymax + tot->ymin) * 0.5f; + diff= curheight * 0.5f; + + cur->ymin= temp - diff; + cur->ymax= temp + diff; + } + else if (cur->ymin < tot->ymin) { + /* there's still space remaining, so shift up */ + temp= tot->ymin - cur->ymin; + + cur->ymin += temp; + cur->ymax += temp; + } + else if (cur->ymax > tot->ymax) { + /* there's still space remaining, so shift down */ + temp= cur->ymax - tot->ymax; + + cur->ymin -= temp; + cur->ymax -= temp; + } + } + } + + /* Step 4: Make sure alignment restrictions are respected */ + if (v2d->align) { + /* If alignment flags are set (but keeptot is not), they must still be respected, as although + * they don't specify any particular bounds to stay within, they do define ranges which are + * invalid. + * + * Here, we only check to make sure that on each axis, the 'cur' rect doesn't stray into these + * invalid zones, otherwise we offset. + */ + + /* handle width - posx and negx flags are mutually exclusive, so watch out */ + if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) { + /* width is in negative-x half */ + if (v2d->cur.xmax > 0) { + v2d->cur.xmin -= v2d->cur.xmax; + v2d->cur.xmax= 0.0f; + } + } + else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) { + /* width is in positive-x half */ + if (v2d->cur.xmin < 0) { + v2d->cur.xmax -= v2d->cur.xmin; + v2d->cur.xmin = 0.0f; + } + } + + /* handle height - posx and negx flags are mutually exclusive, so watch out */ + if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) { + /* height is in negative-y half */ + if (v2d->cur.ymax > 0) { + v2d->cur.ymin -= v2d->cur.ymax; + v2d->cur.ymax = 0.0f; + } + } + else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) { + /* height is in positive-y half */ + if (v2d->cur.ymin < 0) { + v2d->cur.ymax -= v2d->cur.ymin; + v2d->cur.ymin = 0.0f; + } + } + } + + /* set masks */ + view2d_masks(v2d); +} + +/* ------------------ */ + +/* Called by menus to activate it, or by view2d operators to make sure 'related' views stay in synchrony */ +void UI_view2d_sync(bScreen *screen, ScrArea *area, View2D *v2dcur, int flag) +{ + ScrArea *sa; + ARegion *ar; + + /* don't continue if no view syncing to be done */ + if ((v2dcur->flag & (V2D_VIEWSYNC_SCREEN_TIME|V2D_VIEWSYNC_AREA_VERTICAL)) == 0) + return; + + /* check if doing within area syncing (i.e. channels/vertical) */ + if ((v2dcur->flag & V2D_VIEWSYNC_AREA_VERTICAL) && (area)) { + for (ar= area->regionbase.first; ar; ar= ar->next) { + /* don't operate on self */ + if (v2dcur != &ar->v2d) { + /* only if view has vertical locks enabled */ + if (ar->v2d.flag & V2D_VIEWSYNC_AREA_VERTICAL) { + if (flag == V2D_LOCK_COPY) { + /* other views with locks on must copy active */ + ar->v2d.cur.ymin= v2dcur->cur.ymin; + ar->v2d.cur.ymax= v2dcur->cur.ymax; + } + else { /* V2D_LOCK_SET */ + /* active must copy others */ + v2dcur->cur.ymin= ar->v2d.cur.ymin; + v2dcur->cur.ymax= ar->v2d.cur.ymax; + } + + /* region possibly changed, so refresh */ + ED_region_tag_redraw(ar); + } + } + } + } + + /* check if doing whole screen syncing (i.e. time/horizontal) */ + if ((v2dcur->flag & V2D_VIEWSYNC_SCREEN_TIME) && (screen)) { + for (sa= screen->areabase.first; sa; sa= sa->next) { + for (ar= sa->regionbase.first; ar; ar= ar->next) { + /* don't operate on self */ + if (v2dcur != &ar->v2d) { + /* only if view has horizontal locks enabled */ + if (ar->v2d.flag & V2D_VIEWSYNC_SCREEN_TIME) { + if (flag == V2D_LOCK_COPY) { + /* other views with locks on must copy active */ + ar->v2d.cur.xmin= v2dcur->cur.xmin; + ar->v2d.cur.xmax= v2dcur->cur.xmax; + } + else { /* V2D_LOCK_SET */ + /* active must copy others */ + v2dcur->cur.xmin= ar->v2d.cur.xmin; + v2dcur->cur.xmax= ar->v2d.cur.xmax; + } + + /* region possibly changed, so refresh */ + ED_region_tag_redraw(ar); + } + } + } + } + } +} + + +/* Restore 'cur' rect to standard orientation (i.e. optimal maximum view of tot) + * This does not take into account if zooming the view on an axis will improve the view (if allowed) + */ +void UI_view2d_curRect_reset (View2D *v2d) +{ + float width, height; + + /* assume width and height of 'cur' rect by default, should be same size as mask */ + width= (float)(v2d->mask.xmax - v2d->mask.xmin + 1); + height= (float)(v2d->mask.ymax - v2d->mask.ymin + 1); + + /* handle width - posx and negx flags are mutually exclusive, so watch out */ + if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) { + /* width is in negative-x half */ + v2d->cur.xmin= (float)-width; + v2d->cur.xmax= 0.0f; + } + else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) { + /* width is in positive-x half */ + v2d->cur.xmin= 0.0f; + v2d->cur.xmax= (float)width; + } + else { + /* width is centered around x==0 */ + const float dx= (float)width / 2.0f; + + v2d->cur.xmin= -dx; + v2d->cur.xmax= dx; + } + + /* handle height - posx and negx flags are mutually exclusive, so watch out */ + if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) { + /* height is in negative-y half */ + v2d->cur.ymin= (float)-height; + v2d->cur.ymax= 0.0f; + } + else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) { + /* height is in positive-y half */ + v2d->cur.ymin= 0.0f; + v2d->cur.ymax= (float)height; + } + else { + /* height is centered around y==0 */ + const float dy= (float)height / 2.0f; + + v2d->cur.ymin= -dy; + v2d->cur.ymax= dy; + } +} + +/* ------------------ */ + +/* Change the size of the maximum viewable area (i.e. 'tot' rect) */ +void UI_view2d_totRect_set (View2D *v2d, int width, int height) +{ + int scroll= view2d_scroll_mapped(v2d->scroll); + + /* don't do anything if either value is 0 */ + width= abs(width); + height= abs(height); + + /* hrumf! */ + if(scroll & V2D_SCROLL_HORIZONTAL) + width -= V2D_SCROLL_WIDTH; + if(scroll & V2D_SCROLL_VERTICAL) + height -= V2D_SCROLL_HEIGHT; + + if (ELEM3(0, v2d, width, height)) { + printf("Error: View2D totRect set exiting: v2d=%p width=%d height=%d \n", v2d, width, height); // XXX temp debug info + return; + } + + /* handle width - posx and negx flags are mutually exclusive, so watch out */ + if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) { + /* width is in negative-x half */ + v2d->tot.xmin= (float)-width; + v2d->tot.xmax= 0.0f; + } + else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) { + /* width is in positive-x half */ + v2d->tot.xmin= 0.0f; + v2d->tot.xmax= (float)width; + } + else { + /* width is centered around x==0 */ + const float dx= (float)width / 2.0f; + + v2d->tot.xmin= -dx; + v2d->tot.xmax= dx; + } + + /* handle height - posx and negx flags are mutually exclusive, so watch out */ + if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) { + /* height is in negative-y half */ + v2d->tot.ymin= (float)-height; + v2d->tot.ymax= 0.0f; + } + else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) { + /* height is in positive-y half */ + v2d->tot.ymin= 0.0f; + v2d->tot.ymax= (float)height; + } + else { + /* height is centered around y==0 */ + const float dy= (float)height / 2.0f; + + v2d->tot.ymin= -dy; + v2d->tot.ymax= dy; + } + + /* make sure that 'cur' rect is in a valid state as a result of these changes */ + UI_view2d_curRect_validate(v2d); +} + +/* *********************************************************************** */ +/* View Matrix Setup */ + +/* mapping function to ensure 'cur' draws extended over the area where sliders are */ +static void view2d_map_cur_using_mask(View2D *v2d, rctf *curmasked) +{ + *curmasked= v2d->cur; + + if (view2d_scroll_mapped(v2d->scroll)) { + float dx= (v2d->cur.xmax-v2d->cur.xmin)/((float)(v2d->mask.xmax-v2d->mask.xmin+1)); + float dy= (v2d->cur.ymax-v2d->cur.ymin)/((float)(v2d->mask.ymax-v2d->mask.ymin+1)); + + if (v2d->mask.xmin != 0) + curmasked->xmin -= dx*(float)v2d->mask.xmin; + if (v2d->mask.xmax+1 != v2d->winx) + curmasked->xmax += dx*(float)(v2d->winx - v2d->mask.xmax-1); + + if (v2d->mask.ymin != 0) + curmasked->ymin -= dy*(float)v2d->mask.ymin; + if (v2d->mask.ymax+1 != v2d->winy) + curmasked->ymax += dy*(float)(v2d->winy - v2d->mask.ymax-1); + + } +} + +/* Set view matrices to use 'cur' rect as viewing frame for View2D drawing */ +void UI_view2d_view_ortho(const bContext *C, View2D *v2d) +{ + rctf curmasked; + float xofs, yofs; + + /* pixel offsets (-0.375f) are needed to get 1:1 correspondance with pixels for smooth UI drawing, + * but only applied where requsted + */ + /* XXX ton: fix this! */ + xofs= 0.0; // (v2d->flag & V2D_PIXELOFS_X) ? 0.375f : 0.0f; + yofs= 0.0; // (v2d->flag & V2D_PIXELOFS_Y) ? 0.375f : 0.0f; + + /* XXX brecht: instead of zero at least use a tiny offset, otherwise + * pixel rounding is effectively random due to float inaccuracy */ + xofs= 0.001f; + yofs= 0.001f; + + /* apply mask-based adjustments to cur rect (due to scrollers), to eliminate scaling artifacts */ + view2d_map_cur_using_mask(v2d, &curmasked); + + /* set matrix on all appropriate axes */ + wmOrtho2(curmasked.xmin-xofs, curmasked.xmax-xofs, curmasked.ymin-yofs, curmasked.ymax-yofs); + + /* XXX is this necessary? */ + wmLoadIdentity(); +} + +/* Set view matrices to only use one axis of 'cur' only + * - xaxis = if non-zero, only use cur x-axis, otherwise use cur-yaxis (mostly this will be used for x) + */ +void UI_view2d_view_orthoSpecial(const bContext *C, View2D *v2d, short xaxis) +{ + ARegion *ar= CTX_wm_region(C); + rctf curmasked; + float xofs, yofs; + + /* pixel offsets (-0.375f) are needed to get 1:1 correspondance with pixels for smooth UI drawing, + * but only applied where requsted + */ + /* XXX temp (ton) */ + xofs= 0.0f; // (v2d->flag & V2D_PIXELOFS_X) ? 0.375f : 0.0f; + yofs= 0.0f; // (v2d->flag & V2D_PIXELOFS_Y) ? 0.375f : 0.0f; + + /* apply mask-based adjustments to cur rect (due to scrollers), to eliminate scaling artifacts */ + view2d_map_cur_using_mask(v2d, &curmasked); + + /* only set matrix with 'cur' coordinates on relevant axes */ + if (xaxis) + wmOrtho2(curmasked.xmin-xofs, curmasked.xmax-xofs, -yofs, ar->winy-yofs); + else + wmOrtho2(-xofs, ar->winx-xofs, curmasked.ymin-yofs, curmasked.ymax-yofs); + + /* XXX is this necessary? */ + wmLoadIdentity(); +} + + +/* Restore view matrices after drawing */ +void UI_view2d_view_restore(const bContext *C) +{ + ED_region_pixelspace(CTX_wm_region(C)); +} + +/* *********************************************************************** */ +/* Gridlines */ + +/* minimum pixels per gridstep */ +#define MINGRIDSTEP 35 + +/* View2DGrid is typedef'd in UI_view2d.h */ +struct View2DGrid { + float dx, dy; /* stepsize (in pixels) between gridlines */ + float startx, starty; /* initial coordinates to start drawing grid from */ + int powerx, powery; /* step as power of 10 */ +}; + +/* --------------- */ + +/* try to write step as a power of 10 */ +static void step_to_grid(float *step, int *power, int unit) +{ + const float loga= (float)log10(*step); + float rem; + + *power= (int)(loga); + + rem= loga - (*power); + rem= (float)pow(10.0, rem); + + if (loga < 0.0f) { + if (rem < 0.2f) rem= 0.2f; + else if(rem < 0.5f) rem= 0.5f; + else rem= 1.0f; + + *step= rem * (float)pow(10.0, (*power)); + + /* for frames, we want 1.0 frame intervals only */ + if (unit == V2D_UNIT_FRAMES) { + rem = 1.0f; + *step = 1.0f; + } + + /* prevents printing 1.0 2.0 3.0 etc */ + if (rem == 1.0f) (*power)++; + } + else { + if (rem < 2.0f) rem= 2.0f; + else if(rem < 5.0f) rem= 5.0f; + else rem= 10.0f; + + *step= rem * (float)pow(10.0, (*power)); + + (*power)++; + /* prevents printing 1.0, 2.0, 3.0, etc. */ + if (rem == 10.0f) (*power)++; + } +} + +/* Intialise settings necessary for drawing gridlines in a 2d-view + * - Currently, will return pointer to View2DGrid struct that needs to + * be freed with UI_view2d_grid_free() + * - Is used for scrollbar drawing too (for units drawing) + * - Units + clamping args will be checked, to make sure they are valid values that can be used + * so it is very possible that we won't return grid at all! + * + * - xunits,yunits = V2D_UNIT_* grid steps in seconds or frames + * - xclamp,yclamp = V2D_CLAMP_* only show whole-number intervals + * - winx = width of region we're drawing to + * - winy = height of region we're drawing into + */ +View2DGrid *UI_view2d_grid_calc(const bContext *C, View2D *v2d, short xunits, short xclamp, short yunits, short yclamp, int winx, int winy) +{ + Scene *scene= CTX_data_scene(C); + View2DGrid *grid; + float space, pixels, seconddiv; + int secondgrid; + + /* check that there are at least some workable args */ + if (ELEM(V2D_ARG_DUMMY, xunits, xclamp) && ELEM(V2D_ARG_DUMMY, yunits, yclamp)) + return NULL; + + /* grid here is allocated... */ + grid= MEM_callocN(sizeof(View2DGrid), "View2DGrid"); + + /* rule: gridstep is minimal GRIDSTEP pixels */ + if (xunits == V2D_UNIT_SECONDS) { + secondgrid= 1; + seconddiv= (float)(0.01 * FPS); + } + else { + secondgrid= 0; + seconddiv= 1.0f; + } + + /* calculate x-axis grid scale (only if both args are valid) */ + if (ELEM(V2D_ARG_DUMMY, xunits, xclamp) == 0) { + space= v2d->cur.xmax - v2d->cur.xmin; + pixels= (float)(v2d->mask.xmax - v2d->mask.xmin); + + grid->dx= (MINGRIDSTEP * space) / (seconddiv * pixels); + step_to_grid(&grid->dx, &grid->powerx, xunits); + grid->dx *= seconddiv; + + if (xclamp == V2D_GRID_CLAMP) { + if (grid->dx < 0.1f) grid->dx= 0.1f; + grid->powerx-= 2; + if (grid->powerx < -2) grid->powerx= -2; + } + } + + /* calculate y-axis grid scale (only if both args are valid) */ + if (ELEM(V2D_ARG_DUMMY, yunits, yclamp) == 0) { + space= v2d->cur.ymax - v2d->cur.ymin; + pixels= (float)winy; + + grid->dy= MINGRIDSTEP * space / pixels; + step_to_grid(&grid->dy, &grid->powery, yunits); + + if (yclamp == V2D_GRID_CLAMP) { + if (grid->dy < 1.0f) grid->dy= 1.0f; + if (grid->powery < 1) grid->powery= 1; + } + } + + /* calculate start position */ + if (ELEM(V2D_ARG_DUMMY, xunits, xclamp) == 0) { + grid->startx= seconddiv*(v2d->cur.xmin/seconddiv - (float)fmod(v2d->cur.xmin/seconddiv, grid->dx/seconddiv)); + if (v2d->cur.xmin < 0.0f) grid->startx-= grid->dx; + } + else + grid->startx= v2d->cur.xmin; + + if (ELEM(V2D_ARG_DUMMY, yunits, yclamp) == 0) { + grid->starty= (v2d->cur.ymin - (float)fmod(v2d->cur.ymin, grid->dy)); + if (v2d->cur.ymin < 0.0f) grid->starty-= grid->dy; + } + else + grid->starty= v2d->cur.ymin; + + return grid; +} + +/* Draw gridlines in the given 2d-region */ +void UI_view2d_grid_draw(const bContext *C, View2D *v2d, View2DGrid *grid, int flag) +{ + float vec1[2], vec2[2]; + int a, step; + + /* check for grid first, as it may not exist */ + if (grid == NULL) + return; + + /* vertical lines */ + if (flag & V2D_VERTICAL_LINES) { + /* initialise initial settings */ + vec1[0]= vec2[0]= grid->startx; + vec1[1]= grid->starty; + vec2[1]= v2d->cur.ymax; + + /* minor gridlines */ + step= (v2d->mask.xmax - v2d->mask.xmin + 1) / MINGRIDSTEP; + UI_ThemeColor(TH_GRID); + + for (a=0; a<step; a++) { + glBegin(GL_LINE_STRIP); + glVertex2fv(vec1); + glVertex2fv(vec2); + glEnd(); + + vec2[0]= vec1[0]+= grid->dx; + } + + /* major gridlines */ + vec2[0]= vec1[0]-= 0.5f*grid->dx; + UI_ThemeColorShade(TH_GRID, 16); + + step++; + for (a=0; a<=step; a++) { + glBegin(GL_LINE_STRIP); + glVertex2fv(vec1); + glVertex2fv(vec2); + glEnd(); + + vec2[0]= vec1[0]-= grid->dx; + } + } + + /* horizontal lines */ + if (flag & V2D_HORIZONTAL_LINES) { + /* only major gridlines */ + vec1[1]= vec2[1]= grid->starty; + vec1[0]= grid->startx; + vec2[0]= v2d->cur.xmax; + + step= (v2d->mask.ymax - v2d->mask.ymin + 1) / MINGRIDSTEP; + + UI_ThemeColor(TH_GRID); + for (a=0; a<=step; a++) { + glBegin(GL_LINE_STRIP); + glVertex2fv(vec1); + glVertex2fv(vec2); + glEnd(); + + vec2[1]= vec1[1]+= grid->dy; + } + + /* fine grid lines */ + vec2[1]= vec1[1]-= 0.5f*grid->dy; + step++; + + if (flag & V2D_HORIZONTAL_FINELINES) { + UI_ThemeColorShade(TH_GRID, 16); + for (a=0; a<step; a++) { + glBegin(GL_LINE_STRIP); + glVertex2fv(vec1); + glVertex2fv(vec2); + glEnd(); + + vec2[1]= vec1[1]-= grid->dy; + } + } + } + + /* Axes are drawn as darker lines */ + UI_ThemeColorShade(TH_GRID, -50); + + /* horizontal axis */ + if (flag & V2D_HORIZONTAL_AXIS) { + vec1[0]= v2d->cur.xmin; + vec2[0]= v2d->cur.xmax; + vec1[1]= vec2[1]= 0.0f; + + glBegin(GL_LINE_STRIP); + glVertex2fv(vec1); + glVertex2fv(vec2); + glEnd(); + } + + /* vertical axis */ + if (flag & V2D_VERTICAL_AXIS) { + vec1[1]= v2d->cur.ymin; + vec2[1]= v2d->cur.ymax; + vec1[0]= vec2[0]= 0.0f; + + glBegin(GL_LINE_STRIP); + glVertex2fv(vec1); + glVertex2fv(vec2); + glEnd(); + } +} + +/* Draw a constant grid in given 2d-region */ +void UI_view2d_constant_grid_draw(const bContext *C, View2D *v2d) +{ + float start, step= 25.0f; + + UI_ThemeColorShade(TH_BACK, -10); + + start= v2d->cur.xmin - (float)fmod(v2d->cur.xmin, step); + + glBegin(GL_LINES); + for(; start<v2d->cur.xmax; start+=step) { + glVertex2f(start, v2d->cur.ymin); + glVertex2f(start, v2d->cur.ymax); + } + + start= v2d->cur.ymin - (float)fmod(v2d->cur.ymin, step); + for(; start<v2d->cur.ymax; start+=step) { + glVertex2f(v2d->cur.xmin, start); + glVertex2f(v2d->cur.xmax, start); + } + + /* X and Y axis */ + UI_ThemeColorShade(TH_BACK, -18); + glVertex2f(0.0f, v2d->cur.ymin); + glVertex2f(0.0f, v2d->cur.ymax); + glVertex2f(v2d->cur.xmin, 0.0f); + glVertex2f(v2d->cur.xmax, 0.0f); + + glEnd(); +} + +/* free temporary memory used for drawing grid */ +void UI_view2d_grid_free(View2DGrid *grid) +{ + /* only free if there's a grid */ + if (grid) + MEM_freeN(grid); +} + +/* *********************************************************************** */ +/* Scrollers */ + +/* View2DScrollers is typedef'd in UI_view2d.h + * WARNING: the start of this struct must not change, as view2d_ops.c uses this too. + * For now, we don't need to have a separate (internal) header for structs like this... + */ +struct View2DScrollers { + /* focus bubbles */ + int vert_min, vert_max; /* vertical scrollbar */ + int hor_min, hor_max; /* horizontal scrollbar */ + + /* scales */ + View2DGrid *grid; /* grid for coordinate drawing */ + short xunits, xclamp; /* units and clamping options for x-axis */ + short yunits, yclamp; /* units and clamping options for y-axis */ +}; + +/* Calculate relevant scroller properties */ +View2DScrollers *UI_view2d_scrollers_calc(const bContext *C, View2D *v2d, short xunits, short xclamp, short yunits, short yclamp) +{ + View2DScrollers *scrollers; + rcti vert, hor; + float fac, totsize, scrollsize; + int scroll= view2d_scroll_mapped(v2d->scroll); + + vert= v2d->vert; + hor= v2d->hor; + + /* scrollers is allocated here... */ + scrollers= MEM_callocN(sizeof(View2DScrollers), "View2DScrollers"); + + /* scroller 'buttons': + * - These should always remain within the visible region of the scrollbar + * - They represent the region of 'tot' that is visible in 'cur' + */ + + /* horizontal scrollers */ + if (scroll & V2D_SCROLL_HORIZONTAL) { + /* scroller 'button' extents */ + totsize= v2d->tot.xmax - v2d->tot.xmin; + scrollsize= (float)(hor.xmax - hor.xmin); + + fac= (v2d->cur.xmin - v2d->tot.xmin) / totsize; + scrollers->hor_min= (int)(hor.xmin + (fac * scrollsize)); + + fac= (v2d->cur.xmax - v2d->tot.xmin) / totsize; + scrollers->hor_max= (int)(hor.xmin + (fac * scrollsize)); + + if (scrollers->hor_min > scrollers->hor_max) + scrollers->hor_min= scrollers->hor_max; + } + + /* vertical scrollers */ + if (scroll & V2D_SCROLL_VERTICAL) { + /* scroller 'button' extents */ + totsize= v2d->tot.ymax - v2d->tot.ymin; + scrollsize= (float)(vert.ymax - vert.ymin); + + fac= (v2d->cur.ymin- v2d->tot.ymin) / totsize; + scrollers->vert_min= (int)(vert.ymin + (fac * scrollsize)); + + fac= (v2d->cur.ymax - v2d->tot.ymin) / totsize; + scrollers->vert_max= (int)(vert.ymin + (fac * scrollsize)); + + if (scrollers->vert_min > scrollers->vert_max) + scrollers->vert_min= scrollers->vert_max; + } + + /* grid markings on scrollbars */ + if (scroll & (V2D_SCROLL_SCALE_HORIZONTAL|V2D_SCROLL_SCALE_VERTICAL)) { + /* store clamping */ + scrollers->xclamp= xclamp; + scrollers->xunits= xunits; + scrollers->yclamp= yclamp; + scrollers->yunits= yunits; + + scrollers->grid= UI_view2d_grid_calc(C, v2d, xunits, xclamp, yunits, yclamp, (hor.xmax - hor.xmin), (vert.ymax - vert.ymin)); + } + + /* return scrollers */ + return scrollers; +} + +/* Print scale marking along a time scrollbar */ +static void scroll_printstr(View2DScrollers *scrollers, Scene *scene, float x, float y, float val, int power, short unit, char dir) +{ + int len; + char str[32]; + + /* adjust the scale unit to work ok */ + if (dir == 'v') { + /* here we bump up the power by factor of 10, as + * rotation values (hence 'degrees') are divided by 10 to + * be able to show the curves at the same time + */ + if ELEM(unit, V2D_UNIT_DEGREES, V2D_UNIT_TIME) { + power += 1; + val *= 10; + } + } + + /* get string to print */ + if (unit == V2D_UNIT_SECONDS) { + /* SMPTE timecode style: + * - In general, minutes and seconds should be shown, as most clips will be + * within this length. Hours will only be included if relevant. + * - Only show frames when zoomed in enough for them to be relevant + * (using separator convention of ';' for frames, ala QuickTime). + * When showing frames, use slightly different display to avoid confusion with mm:ss format + */ + int hours=0, minutes=0, seconds=0, frames=0; + char neg[2]= ""; + + /* get values */ + if (val < 0) { + /* correction for negative values */ + sprintf(neg, "-"); + val = -val; + } + if (val >= 3600) { + /* hours */ + /* XXX should we only display a single digit for hours since clips are + * VERY UNLIKELY to be more than 1-2 hours max? However, that would + * go against conventions... + */ + hours= (int)val / 3600; + val= (float)fmod(val, 3600); + } + if (val >= 60) { + /* minutes */ + minutes= (int)val / 60; + val= (float)fmod(val, 60); + } + if (power <= 0) { + /* seconds + frames + * Frames are derived from 'fraction' of second. We need to perform some additional rounding + * to cope with 'half' frames, etc., which should be fine in most cases + */ + seconds= (int)val; + frames= (int)floor( ((val - seconds) * FPS) + 0.5f ); + } + else { + /* seconds (with pixel offset) */ + seconds= (int)floor(val + 0.375f); + } + + /* print timecode to temp string buffer */ + if (power <= 0) { + /* include "frames" in display */ + if (hours) sprintf(str, "%s%02d:%02d:%02d;%02d", neg, hours, minutes, seconds, frames); + else if (minutes) sprintf(str, "%s%02d:%02d;%02d", neg, minutes, seconds, frames); + else sprintf(str, "%s%d;%02d", neg, seconds, frames); + } + else { + /* don't include 'frames' in display */ + if (hours) sprintf(str, "%s%02d:%02d:%02d", neg, hours, minutes, seconds); + else sprintf(str, "%s%02d:%02d", neg, minutes, seconds); + } + } + else { + /* round to whole numbers if power is >= 1 (i.e. scale is coarse) */ + if (power <= 0) sprintf(str, "%.*f", 1-power, val); + else sprintf(str, "%d", (int)floor(val + 0.375f)); + } + + /* get length of string, and adjust printing location to fit it into the horizontal scrollbar */ + len= strlen(str); + if (dir == 'h') { + /* seconds/timecode display has slightly longer strings... */ + if (unit == V2D_UNIT_SECONDS) + x-= 3*len; + else + x-= 4*len; + } + + /* Add degree sympbol to end of string for vertical scrollbar? */ + if ((dir == 'v') && (unit == V2D_UNIT_DEGREES)) { + str[len]= 186; + str[len+1]= 0; + } + + /* draw it */ + BLF_draw_default(x, y, 0.0f, str); +} + +/* local defines for scrollers drawing */ + /* radius of scroller 'button' caps */ +#define V2D_SCROLLCAP_RAD 5 + /* shading factor for scroller 'bar' */ +#define V2D_SCROLLBAR_SHADE 0.1f + /* shading factor for scroller 'button' caps */ +#define V2D_SCROLLCAP_SHADE 0.2f + +/* Draw scrollbars in the given 2d-region */ +void UI_view2d_scrollers_draw(const bContext *C, View2D *v2d, View2DScrollers *vs) +{ + Scene *scene= CTX_data_scene(C); + const short darker= -50, dark= -10, light= 20, lighter= 50; + rcti vert, hor, corner; + int scroll= view2d_scroll_mapped(v2d->scroll); + + /* make copies of rects for less typing */ + vert= v2d->vert; + hor= v2d->hor; + + /* horizontal scrollbar */ + if (scroll & V2D_SCROLL_HORIZONTAL) { + /* scroller backdrop */ + UI_ThemeColorShade(TH_SHADE1, light); + glRecti(hor.xmin, hor.ymin, hor.xmax, hor.ymax); + + /* scroller 'button' + * - if view is zoomable in x, draw handles too + * - handles are drawn darker + * - no slider when view is > total for non-zoomable views + * (otherwise, zoomable ones tend to flicker) + */ + if ( (v2d->scroll & V2D_SCROLL_SCALE_HORIZONTAL) || + ((v2d->tot.xmax - v2d->tot.xmin) > (v2d->cur.xmax - v2d->cur.xmin)) ) + { + if (v2d->keepzoom & V2D_LOCKZOOM_X) { + /* draw base bar as rounded shape */ + UI_ThemeColorShade(TH_SHADE1, dark); + uiSetRoundBox(15); + + /* check that box is large enough for round drawing */ + if ((vs->hor_max - vs->hor_min) < (V2D_SCROLLCAP_RAD * 2)) { + /* Rounded box still gets drawn at the minimum size limit + * This doesn't represent extreme scaling well, but looks nicer... + */ + float mid= 0.5f * (vs->hor_max + vs->hor_min); + + gl_round_box_shade(GL_POLYGON, + mid-V2D_SCROLLCAP_RAD, (float)hor.ymin+2, + mid+V2D_SCROLLCAP_RAD, (float)hor.ymax-2, + V2D_SCROLLCAP_RAD, V2D_SCROLLBAR_SHADE, -V2D_SCROLLBAR_SHADE); + } + else { + /* draw rounded box as per normal */ + gl_round_box_shade(GL_POLYGON, + (float)vs->hor_min, (float)hor.ymin+2, + (float)vs->hor_max, (float)hor.ymax-2, + V2D_SCROLLCAP_RAD, V2D_SCROLLBAR_SHADE, -V2D_SCROLLBAR_SHADE); + } + } + else { + /* base bar drawn as shaded rect */ + UI_ThemeColorShade(TH_SHADE1, dark); + uiSetRoundBox(0); + gl_round_box_shade(GL_POLYGON, + (float)vs->hor_min, (float)hor.ymin+2, + (float)vs->hor_max, (float)hor.ymax-2, + V2D_SCROLLCAP_RAD, V2D_SCROLLBAR_SHADE, -V2D_SCROLLBAR_SHADE); + + /* 'minimum' handle */ + uiSetRoundBox(9); + UI_ThemeColorShade(TH_SHADE1, darker); + + gl_round_box_shade(GL_POLYGON, + (float)vs->hor_min-V2D_SCROLLER_HANDLE_SIZE, (float)hor.ymin+2, + (float)vs->hor_min+V2D_SCROLLER_HANDLE_SIZE, (float)hor.ymax-2, + V2D_SCROLLCAP_RAD, V2D_SCROLLCAP_SHADE, -V2D_SCROLLCAP_SHADE); + + /* maximum handle */ + uiSetRoundBox(6); + UI_ThemeColorShade(TH_SHADE1, darker); + + gl_round_box_shade(GL_POLYGON, + (float)vs->hor_max-V2D_SCROLLER_HANDLE_SIZE, (float)hor.ymin+2, + (float)vs->hor_max+V2D_SCROLLER_HANDLE_SIZE, (float)hor.ymax-2, + V2D_SCROLLCAP_RAD, V2D_SCROLLCAP_SHADE, -V2D_SCROLLCAP_SHADE); + } + } + + /* scale indicators */ + // XXX will need to update the font drawing when the new stuff comes in + if ((scroll & V2D_SCROLL_SCALE_HORIZONTAL) && (vs->grid)) { + View2DGrid *grid= vs->grid; + float fac, dfac, fac2, val; + + /* the numbers: convert grid->startx and -dx to scroll coordinates + * - fac is x-coordinate to draw to + * - dfac is gap between scale markings + */ + fac= (grid->startx - v2d->cur.xmin) / (v2d->cur.xmax - v2d->cur.xmin); + fac= (float)hor.xmin + fac*(hor.xmax - hor.xmin); + + dfac= (grid->dx) / (v2d->cur.xmax - v2d->cur.xmin); + dfac= dfac * (hor.xmax - hor.xmin); + + /* set starting value, and text color */ + UI_ThemeColor(TH_TEXT); + val= grid->startx; + + /* if we're clamping to whole numbers only, make sure entries won't be repeated */ + if (vs->xclamp == V2D_GRID_CLAMP) { + while (grid->dx < 0.9999f) { + grid->dx *= 2.0f; + dfac *= 2.0f; + } + } + if (vs->xunits == V2D_UNIT_FRAMES) + grid->powerx= 1; + + /* draw numbers in the appropriate range */ + if (dfac > 0.0f) { + for (; fac < hor.xmax; fac+=dfac, val+=grid->dx) { + switch (vs->xunits) { + case V2D_UNIT_FRAMES: /* frames (as whole numbers)*/ + scroll_printstr(vs, scene, fac, 3.0f+(float)(hor.ymin), val, grid->powerx, V2D_UNIT_FRAMES, 'h'); + break; + + case V2D_UNIT_FRAMESCALE: /* frames (not always as whole numbers) */ + scroll_printstr(vs, scene, fac, 3.0f+(float)(hor.ymin), val, grid->powerx, V2D_UNIT_FRAMESCALE, 'h'); + break; + + case V2D_UNIT_SECONDS: /* seconds */ + fac2= val/(float)FPS; + scroll_printstr(vs, scene, fac, 3.0f+(float)(hor.ymin), fac2, grid->powerx, V2D_UNIT_SECONDS, 'h'); + break; + + case V2D_UNIT_SECONDSSEQ: /* seconds with special calculations (only used for sequencer only) */ + { + float time; + + fac2= val/(float)FPS; + time= (float)floor(fac2); + fac2= fac2-time; + + scroll_printstr(vs, scene, fac, 3.0f+(float)(hor.ymin), time+(float)FPS*fac2/100.0f, grid->powerx, V2D_UNIT_SECONDSSEQ, 'h'); + } + break; + + case V2D_UNIT_DEGREES: /* Graph Editor for rotation Drivers */ + /* HACK: although we're drawing horizontal, we make this draw as 'vertical', just to get degree signs */ + scroll_printstr(vs, scene, fac, 3.0f+(float)(hor.ymin), val, grid->powerx, V2D_UNIT_DEGREES, 'v'); + break; + } + } + } + } + + /* decoration outer bevel line */ + UI_ThemeColorShade(TH_SHADE1, lighter); + if (scroll & (V2D_SCROLL_BOTTOM|V2D_SCROLL_BOTTOM_O)) + sdrawline(hor.xmin, hor.ymax, hor.xmax, hor.ymax); + else if (scroll & V2D_SCROLL_TOP) + sdrawline(hor.xmin, hor.ymin, hor.xmax, hor.ymin); + } + + /* vertical scrollbar */ + if (scroll & V2D_SCROLL_VERTICAL) { + /* scroller backdrop */ + UI_ThemeColorShade(TH_SHADE1, light); + glRecti(vert.xmin, vert.ymin, vert.xmax, vert.ymax); + + /* scroller 'button' + * - if view is zoomable in y, draw handles too + * - handles are drawn darker + * - no slider when view is > total for non-zoomable views + * (otherwise, zoomable ones tend to flicker) + */ + if ( (v2d->scroll & V2D_SCROLL_SCALE_VERTICAL) || + ((v2d->tot.ymax - v2d->tot.ymin) > (v2d->cur.ymax - v2d->cur.ymin)) ) + { + if (v2d->keepzoom & V2D_LOCKZOOM_Y) { + /* draw base bar as rounded shape */ + UI_ThemeColorShade(TH_SHADE1, dark); + uiSetRoundBox(15); + + /* check that box is large enough for round drawing */ + if ((vs->vert_max - vs->vert_min) < (V2D_SCROLLCAP_RAD * 2)) { + /* Rounded box still gets drawn at the minimum size limit + * This doesn't represent extreme scaling well, but looks nicer... + */ + float mid= 0.5f * (vs->vert_max + vs->vert_min); + + gl_round_box_vertical_shade(GL_POLYGON, + (float)vert.xmin+2, mid-V2D_SCROLLCAP_RAD, + (float)vert.xmax-2, mid+V2D_SCROLLCAP_RAD, + V2D_SCROLLCAP_RAD, V2D_SCROLLBAR_SHADE, -V2D_SCROLLBAR_SHADE); + } + else { + /* draw rounded box as per normal */ + gl_round_box_vertical_shade(GL_POLYGON, + (float)vert.xmin+2, (float)vs->vert_min, + (float)vert.xmax-2, (float)vs->vert_max, + V2D_SCROLLCAP_RAD, V2D_SCROLLBAR_SHADE, -V2D_SCROLLBAR_SHADE); + } + } + else { + /* base bar drawn as shaded rect */ + UI_ThemeColorShade(TH_SHADE1, dark); + uiSetRoundBox(0); + gl_round_box_vertical_shade(GL_POLYGON, + (float)vert.xmin+2, (float)vs->vert_min, + (float)vert.xmax-2, (float)vs->vert_max, + V2D_SCROLLCAP_RAD, V2D_SCROLLBAR_SHADE, -V2D_SCROLLBAR_SHADE); + + /* 'minimum' handle */ + UI_ThemeColorShade(TH_SHADE1, darker); + uiSetRoundBox(12); + + gl_round_box_vertical_shade(GL_POLYGON, + (float)vert.xmin+2, (float)vs->vert_min-V2D_SCROLLER_HANDLE_SIZE, + (float)vert.xmax-2, (float)vs->vert_min+V2D_SCROLLER_HANDLE_SIZE, + V2D_SCROLLCAP_RAD, V2D_SCROLLCAP_SHADE, -V2D_SCROLLCAP_SHADE); + + /* maximum handle */ + UI_ThemeColorShade(TH_SHADE1, darker); + uiSetRoundBox(3); + + gl_round_box_vertical_shade(GL_POLYGON, + (float)vert.xmin+2, (float)vs->vert_max-V2D_SCROLLER_HANDLE_SIZE, + (float)vert.xmax-2, (float)vs->vert_max+V2D_SCROLLER_HANDLE_SIZE, + V2D_SCROLLCAP_RAD, V2D_SCROLLCAP_SHADE, -V2D_SCROLLCAP_SHADE); + } + } + + /* scale indiators */ + // XXX will need to update the font drawing when the new stuff comes in + if ((scroll & V2D_SCROLL_SCALE_VERTICAL) && (vs->grid)) { + View2DGrid *grid= vs->grid; + float fac, dfac, val; + + /* the numbers: convert grid->starty and dy to scroll coordinates + * - fac is y-coordinate to draw to + * - dfac is gap between scale markings + * - these involve a correction for horizontal scrollbar + * NOTE: it's assumed that that scrollbar is there if this is involved! + */ + fac= (grid->starty- v2d->cur.ymin) / (v2d->cur.ymax - v2d->cur.ymin); + fac= (vert.ymin + V2D_SCROLL_HEIGHT) + fac*(vert.ymax - vert.ymin - V2D_SCROLL_HEIGHT); + + dfac= (grid->dy) / (v2d->cur.ymax - v2d->cur.ymin); + dfac= dfac * (vert.ymax - vert.ymin - V2D_SCROLL_HEIGHT); + + /* set starting value, and text color */ + UI_ThemeColor(TH_TEXT); + val= grid->starty; + + /* if vertical clamping (to whole numbers) is used (i.e. in Sequencer), apply correction */ + // XXX only relevant to Sequencer, so need to review this when we port that code + if (vs->yclamp == V2D_GRID_CLAMP) + fac += 0.5f * dfac; + + /* draw vertical steps */ + if (dfac > 0.0f) { + for (; fac < vert.ymax; fac+= dfac, val += grid->dy) { + scroll_printstr(vs, scene, (float)(vert.xmax)-14.0f, fac, val, grid->powery, vs->yunits, 'v'); + } + } + } + + /* decoration outer bevel line */ + UI_ThemeColorShade(TH_SHADE1, lighter); + if (scroll & V2D_SCROLL_RIGHT) + sdrawline(vert.xmin, vert.ymin, vert.xmin, vert.ymax); + else if (scroll & V2D_SCROLL_LEFT) + sdrawline(vert.xmax, vert.ymin, vert.xmax, vert.ymax); + } + + /* draw a 'sunken square' to cover up any overlapping corners resulting from intersection of overflowing scroller data */ + if ((scroll & V2D_SCROLL_VERTICAL) && (scroll & V2D_SCROLL_HORIZONTAL)) { + /* set bounds (these should be right) */ + corner.xmin= vert.xmin; + corner.xmax= vert.xmax; + corner.ymin= hor.ymin; + corner.ymax= hor.ymax; + + /* firstly, draw using background color to cover up any overlapping junk */ + UI_ThemeColor(TH_SHADE1); + glRecti(corner.xmin, corner.ymin, corner.xmax, corner.ymax); + + /* now, draw suggestive highlighting... */ + /* first, dark lines on top to suggest scrollers overlap box */ + UI_ThemeColorShade(TH_SHADE1, darker); + sdrawline(corner.xmin, corner.ymin, corner.xmin, corner.ymax); + sdrawline(corner.xmin, corner.ymax, corner.xmax, corner.ymax); + /* now, light lines on bottom to show box is sunken in */ + UI_ThemeColorShade(TH_SHADE1, lighter); + sdrawline(corner.xmax, corner.ymin, corner.xmax, corner.ymax); + sdrawline(corner.xmin, corner.ymin, corner.xmax, corner.ymin); + } +} + +/* free temporary memory used for drawing scrollers */ +void UI_view2d_scrollers_free(View2DScrollers *scrollers) +{ + /* need to free grid as well... */ + if (scrollers->grid) MEM_freeN(scrollers->grid); + MEM_freeN(scrollers); +} + +/* *********************************************************************** */ +/* List View Utilities */ + +/* Get the view-coordinates of the nominated cell + * - columnwidth, rowheight = size of each 'cell' + * - startx, starty = coordinates (in 'tot' rect space) that the list starts from + * This should be (0,0) for most views. However, for those where the starting row was offsetted + * (like for Animation Editor channel lists, to make the first entry more visible), these will be + * the min-coordinates of the first item. + * - column, row = the 2d-corodinates (in 2D-view / 'tot' rect space) the cell exists at + * - rect = coordinates of the cell (passed as single var instead of 4 separate, as it's more useful this way) + */ +void UI_view2d_listview_cell_to_view(View2D *v2d, short columnwidth, short rowheight, float startx, float starty, int column, int row, rctf *rect) +{ + /* sanity checks */ + if ELEM(NULL, v2d, rect) + return; + if ((columnwidth <= 0) && (rowheight <= 0)) { + rect->xmin= rect->xmax= 0.0f; + rect->ymin= rect->ymax= 0.0f; + return; + } + + /* x-coordinates */ + rect->xmin= startx + (float)(columnwidth * column); + rect->xmax= startx + (float)(columnwidth * (column + 1)); + + if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) { + /* simply negate the values for the coordinates if in negative half */ + rect->xmin = -rect->xmin; + rect->xmax = -rect->xmax; + } + + /* y-coordinates */ + rect->ymin= starty + (float)(rowheight * row); + rect->ymax= starty + (float)(rowheight * (row + 1)); + + if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) { + /* simply negate the values for the coordinates if in negative half */ + rect->ymin = -rect->ymin; + rect->ymax = -rect->ymax; + } +} + +/* Get the 'cell' (row, column) that the given 2D-view coordinates (i.e. in 'tot' rect space) lie in. + * - columnwidth, rowheight = size of each 'cell' + * - startx, starty = coordinates (in 'tot' rect space) that the list starts from + * This should be (0,0) for most views. However, for those where the starting row was offsetted + * (like for Animation Editor channel lists, to make the first entry more visible), these will be + * the min-coordinates of the first item. + * - viewx, viewy = 2D-coordinates (in 2D-view / 'tot' rect space) to get the cell for + * - column, row = the 'coordinates' of the relevant 'cell' + */ +void UI_view2d_listview_view_to_cell(View2D *v2d, short columnwidth, short rowheight, float startx, float starty, + float viewx, float viewy, int *column, int *row) +{ + /* adjust view coordinates to be all positive ints, corrected for the start offset */ + const int x= (int)(floor(fabs(viewx) + 0.5f) - startx); + const int y= (int)(floor(fabs(viewy) + 0.5f) - starty); + + /* sizes must not be negative */ + if ( (v2d == NULL) || ((columnwidth <= 0) && (rowheight <= 0)) ) { + if (column) *column= 0; + if (row) *row= 0; + + return; + } + + /* get column */ + if ((column) && (columnwidth > 0)) + *column= x / columnwidth; + else if (column) + *column= 0; + + /* get row */ + if ((row) && (rowheight > 0)) + *row= y / rowheight; + else if (row) + *row= 0; +} + +/* Get the 'extreme' (min/max) column and row indices which are visible within the 'cur' rect + * - columnwidth, rowheight = size of each 'cell' + * - startx, starty = coordinates that the list starts from, which should be (0,0) for most views + * - column/row_min/max = the starting and ending column/row indices + */ +void UI_view2d_listview_visible_cells(View2D *v2d, short columnwidth, short rowheight, float startx, float starty, + int *column_min, int *column_max, int *row_min, int *row_max) +{ + /* using 'cur' rect coordinates, call the cell-getting function to get the cells for this */ + if (v2d) { + /* min */ + UI_view2d_listview_view_to_cell(v2d, columnwidth, rowheight, startx, starty, + v2d->cur.xmin, v2d->cur.ymin, column_min, row_min); + + /* max*/ + UI_view2d_listview_view_to_cell(v2d, columnwidth, rowheight, startx, starty, + v2d->cur.xmax, v2d->cur.ymax, column_max, row_max); + } +} + +/* *********************************************************************** */ +/* Coordinate Conversions */ + +/* Convert from screen/region space to 2d-View space + * + * - x,y = coordinates to convert + * - viewx,viewy = resultant coordinates + */ +void UI_view2d_region_to_view(View2D *v2d, int x, int y, float *viewx, float *viewy) +{ + float div, ofs; + + if (viewx) { + div= (float)(v2d->mask.xmax - v2d->mask.xmin); + ofs= (float)v2d->mask.xmin; + + *viewx= v2d->cur.xmin + (v2d->cur.xmax-v2d->cur.xmin) * ((float)x - ofs) / div; + } + + if (viewy) { + div= (float)(v2d->mask.ymax - v2d->mask.ymin); + ofs= (float)v2d->mask.ymin; + + *viewy= v2d->cur.ymin + (v2d->cur.ymax - v2d->cur.ymin) * ((float)y - ofs) / div; + } +} + +/* Convert from 2d-View space to screen/region space + * - Coordinates are clamped to lie within bounds of region + * + * - x,y = coordinates to convert + * - regionx,regiony = resultant coordinates + */ +void UI_view2d_view_to_region(View2D *v2d, float x, float y, int *regionx, int *regiony) +{ + /* set initial value in case coordinate lies outside of bounds */ + if (regionx) + *regionx= V2D_IS_CLIPPED; + if (regiony) + *regiony= V2D_IS_CLIPPED; + + /* express given coordinates as proportional values */ + x= (x - v2d->cur.xmin) / (v2d->cur.xmax - v2d->cur.xmin); + y= (y - v2d->cur.ymin) / (v2d->cur.ymax - v2d->cur.ymin); + + /* check if values are within bounds */ + if ((x>=0.0f) && (x<=1.0f) && (y>=0.0f) && (y<=1.0f)) { + if (regionx) + *regionx= (int)(v2d->mask.xmin + x*(v2d->mask.xmax-v2d->mask.xmin)); + if (regiony) + *regiony= (int)(v2d->mask.ymin + y*(v2d->mask.ymax-v2d->mask.ymin)); + } +} + +/* Convert from 2d-view space to screen/region space + * - Coordinates are NOT clamped to lie within bounds of region + * + * - x,y = coordinates to convert + * - regionx,regiony = resultant coordinates + */ +void UI_view2d_to_region_no_clip(View2D *v2d, float x, float y, int *regionx, int *regiony) +{ + /* step 1: express given coordinates as proportional values */ + x= (x - v2d->cur.xmin) / (v2d->cur.xmax - v2d->cur.xmin); + y= (y - v2d->cur.ymin) / (v2d->cur.ymax - v2d->cur.ymin); + + /* step 2: convert proportional distances to screen coordinates */ + x= v2d->mask.xmin + x*(v2d->mask.xmax - v2d->mask.xmin); + y= v2d->mask.ymin + y*(v2d->mask.ymax - v2d->mask.ymin); + + /* although we don't clamp to lie within region bounds, we must avoid exceeding size of ints */ + if (regionx) { + if (x < INT_MIN) *regionx= INT_MIN; + else if(x > INT_MAX) *regionx= INT_MAX; + else *regionx= (int)x; + } + if (regiony) { + if (y < INT_MIN) *regiony= INT_MIN; + else if(y > INT_MAX) *regiony= INT_MAX; + else *regiony= (int)y; + } +} + +/* *********************************************************************** */ +/* Utilities */ + +/* View2D data by default resides in region, so get from region stored in context */ +View2D *UI_view2d_fromcontext(const bContext *C) +{ + ScrArea *area= CTX_wm_area(C); + ARegion *region= CTX_wm_region(C); + + if (area == NULL) return NULL; + if (region == NULL) return NULL; + return &(region->v2d); +} + +/* same as above, but it returns regionwindow. Utility for pulldowns or buttons */ +View2D *UI_view2d_fromcontext_rwin(const bContext *C) +{ + ScrArea *area= CTX_wm_area(C); + ARegion *region= CTX_wm_region(C); + + if (area == NULL) return NULL; + if (region == NULL) return NULL; + if (region->regiontype!=RGN_TYPE_WINDOW) { + ARegion *ar= area->regionbase.first; + for(; ar; ar= ar->next) + if(ar->regiontype==RGN_TYPE_WINDOW) + return &(ar->v2d); + return NULL; + } + return &(region->v2d); +} + + +/* Calculate the scale per-axis of the drawing-area + * - Is used to inverse correct drawing of icons, etc. that need to follow view + * but not be affected by scale + * + * - x,y = scale on each axis + */ +void UI_view2d_getscale(View2D *v2d, float *x, float *y) +{ + if (x) *x = (v2d->mask.xmax - v2d->mask.xmin) / (v2d->cur.xmax - v2d->cur.xmin); + if (y) *y = (v2d->mask.ymax - v2d->mask.ymin) / (v2d->cur.ymax - v2d->cur.ymin); +} + +/* Check if mouse is within scrollers + * - Returns appropriate code for match + * 'h' = in horizontal scroller + * 'v' = in vertical scroller + * 0 = not in scroller + * + * - x,y = mouse coordinates in screen (not region) space + */ +short UI_view2d_mouse_in_scrollers (const bContext *C, View2D *v2d, int x, int y) +{ + ARegion *ar= CTX_wm_region(C); + int co[2]; + int scroll= view2d_scroll_mapped(v2d->scroll); + + /* clamp x,y to region-coordinates first */ + co[0]= x - ar->winrct.xmin; + co[1]= y - ar->winrct.ymin; + + /* check if within scrollbars */ + if (scroll & V2D_SCROLL_HORIZONTAL) { + if (IN_2D_HORIZ_SCROLL(v2d, co)) return 'h'; + } + if (scroll & V2D_SCROLL_VERTICAL) { + if (IN_2D_VERT_SCROLL(v2d, co)) return 'v'; + } + + /* not found */ + return 0; +} + +/* ******************* view2d text drawing cache ******************** */ + +/* assumes caches are used correctly, so for time being no local storage in v2d */ +static ListBase strings= {NULL, NULL}; + +typedef struct View2DString { + struct View2DString *next, *prev; + float col[4]; + char str[128]; + short mval[2]; + rcti rect; +} View2DString; + + +void UI_view2d_text_cache_add(View2D *v2d, float x, float y, char *str) +{ + int mval[2]; + + UI_view2d_view_to_region(v2d, x, y, mval, mval+1); + + if(mval[0]!=V2D_IS_CLIPPED && mval[1]!=V2D_IS_CLIPPED) { + /* use calloc, rect has to be zeroe'd */ + View2DString *v2s= MEM_callocN(sizeof(View2DString), "View2DString"); + + BLI_addtail(&strings, v2s); + BLI_strncpy(v2s->str, str, 128); + v2s->mval[0]= mval[0]; + v2s->mval[1]= mval[1]; + glGetFloatv(GL_CURRENT_COLOR, v2s->col); + } +} + +/* no clip (yet) */ +void UI_view2d_text_cache_rectf(View2D *v2d, rctf *rect, char *str) +{ + View2DString *v2s= MEM_callocN(sizeof(View2DString), "View2DString"); + + UI_view2d_to_region_no_clip(v2d, rect->xmin, rect->ymin, &v2s->rect.xmin, &v2s->rect.ymin); + UI_view2d_to_region_no_clip(v2d, rect->xmax, rect->ymax, &v2s->rect.xmax, &v2s->rect.ymax); + + BLI_addtail(&strings, v2s); + BLI_strncpy(v2s->str, str, 128); + glGetFloatv(GL_CURRENT_COLOR, v2s->col); +} + + +void UI_view2d_text_cache_draw(ARegion *ar) +{ + View2DString *v2s; + + // wmPushMatrix(); + ED_region_pixelspace(ar); + + for(v2s= strings.first; v2s; v2s= v2s->next) { + glColor3fv(v2s->col); + if(v2s->rect.xmin==v2s->rect.xmax) + BLF_draw_default((float)v2s->mval[0], (float)v2s->mval[1], 0.0, v2s->str); + else { + int xofs=0, yofs; + + yofs= ceil( 0.5f*(v2s->rect.ymax - v2s->rect.ymin - BLF_height_default("28"))); + if(yofs<1) yofs= 1; + + BLF_clipping(v2s->rect.xmin-4, v2s->rect.ymin-4, v2s->rect.xmax+4, v2s->rect.ymax+4); + BLF_enable(BLF_CLIPPING); + + BLF_draw_default(v2s->rect.xmin+xofs, v2s->rect.ymin+yofs, 0.0f, v2s->str); + + BLF_disable(BLF_CLIPPING); + } + } + + // wmPopMatrix(); + + if(strings.first) + BLI_freelistN(&strings); +} + + +/* ******************************************************** */ + + diff --git a/source/blender/editors/interface/view2d_ops.c b/source/blender/editors/interface/view2d_ops.c new file mode 100644 index 00000000000..bd1c734b870 --- /dev/null +++ b/source/blender/editors/interface/view2d_ops.c @@ -0,0 +1,1399 @@ +/** + * $Id$ + * + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2008 Blender Foundation. + * All rights reserved. + * + * Contributor(s): Blender Foundation, Joshua Leung + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include <math.h> + +#include "MEM_guardedalloc.h" + +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" +#include "DNA_userdef_types.h" +#include "DNA_vec_types.h" +#include "DNA_view2d_types.h" + +#include "BLI_blenlib.h" + +#include "BKE_context.h" +#include "BKE_utildefines.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "BIF_gl.h" + +#include "ED_screen.h" + +#include "UI_resources.h" +#include "UI_view2d.h" + +static int view2d_poll(bContext *C) +{ + ARegion *ar= CTX_wm_region(C); + + return (ar != NULL) && (ar->v2d.flag & V2D_IS_INITIALISED); +} + +/* ********************************************************* */ +/* VIEW PANNING OPERATOR */ + +/* This group of operators come in several forms: + * 1) Modal 'dragging' with MMB - where movement of mouse dictates amount to pan view by + * 2) Scrollwheel 'steps' - rolling mousewheel by one step moves view by predefined amount + * + * In order to make sure this works, each operator must define the following RNA-Operator Props: + * deltax, deltay - define how much to move view by (relative to zoom-correction factor) + */ + +/* ------------------ Shared 'core' stuff ---------------------- */ + +/* temp customdata for operator */ +typedef struct v2dViewPanData { + bScreen *sc; /* screen where view pan was initiated */ + ScrArea *sa; /* area where view pan was initiated */ + View2D *v2d; /* view2d we're operating in */ + + float facx, facy; /* amount to move view relative to zoom */ + + /* options for version 1 */ + int startx, starty; /* mouse x/y values in window when operator was initiated */ + int lastx, lasty; /* previous x/y values of mouse in window */ + + short in_scroller; /* for MMB in scrollers (old feature in past, but now not that useful) */ +} v2dViewPanData; + +/* initialise panning customdata */ +static int view_pan_init(bContext *C, wmOperator *op) +{ + ARegion *ar= CTX_wm_region(C); + v2dViewPanData *vpd; + View2D *v2d; + float winx, winy; + + /* regions now have v2d-data by default, so check for region */ + if (ar == NULL) + return 0; + + /* check if panning is allowed at all */ + v2d= &ar->v2d; + if ((v2d->keepofs & V2D_LOCKOFS_X) && (v2d->keepofs & V2D_LOCKOFS_Y)) + return 0; + + /* set custom-data for operator */ + vpd= MEM_callocN(sizeof(v2dViewPanData), "v2dViewPanData"); + op->customdata= vpd; + + /* set pointers to owners */ + vpd->sc= CTX_wm_screen(C); + vpd->sa= CTX_wm_area(C); + vpd->v2d= v2d; + + /* calculate translation factor - based on size of view */ + winx= (float)(ar->winrct.xmax - ar->winrct.xmin + 1); + winy= (float)(ar->winrct.ymax - ar->winrct.ymin + 1); + vpd->facx= (v2d->cur.xmax - v2d->cur.xmin) / winx; + vpd->facy= (v2d->cur.ymax - v2d->cur.ymin) / winy; + + return 1; +} + +/* apply transform to view (i.e. adjust 'cur' rect) */ +static void view_pan_apply(bContext *C, wmOperator *op) +{ + v2dViewPanData *vpd= op->customdata; + View2D *v2d= vpd->v2d; + float dx, dy; + + /* calculate amount to move view by */ + dx= vpd->facx * (float)RNA_int_get(op->ptr, "deltax"); + dy= vpd->facy * (float)RNA_int_get(op->ptr, "deltay"); + + /* only move view on an axis if change is allowed */ + if ((v2d->keepofs & V2D_LOCKOFS_X)==0) { + v2d->cur.xmin += dx; + v2d->cur.xmax += dx; + } + if ((v2d->keepofs & V2D_LOCKOFS_Y)==0) { + v2d->cur.ymin += dy; + v2d->cur.ymax += dy; + } + + /* validate that view is in valid configuration after this operation */ + UI_view2d_curRect_validate(v2d); + + /* request updates to be done... */ + ED_area_tag_redraw(vpd->sa); + UI_view2d_sync(vpd->sc, vpd->sa, v2d, V2D_LOCK_COPY); +} + +/* cleanup temp customdata */ +static void view_pan_exit(bContext *C, wmOperator *op) +{ + if (op->customdata) { + MEM_freeN(op->customdata); + op->customdata= NULL; + } +} + +/* ------------------ Modal Drag Version (1) ---------------------- */ + +/* for 'redo' only, with no user input */ +static int view_pan_exec(bContext *C, wmOperator *op) +{ + if (!view_pan_init(C, op)) + return OPERATOR_CANCELLED; + + view_pan_apply(C, op); + view_pan_exit(C, op); + return OPERATOR_FINISHED; +} + +/* set up modal operator and relevant settings */ +static int view_pan_invoke(bContext *C, wmOperator *op, wmEvent *event) +{ + wmWindow *window= CTX_wm_window(C); + v2dViewPanData *vpd; + View2D *v2d; + + /* set up customdata */ + if (!view_pan_init(C, op)) + return OPERATOR_PASS_THROUGH; + + vpd= op->customdata; + v2d= vpd->v2d; + + /* set initial settings */ + vpd->startx= vpd->lastx= event->x; + vpd->starty= vpd->lasty= event->y; + RNA_int_set(op->ptr, "deltax", 0); + RNA_int_set(op->ptr, "deltay", 0); + + if (v2d->keepofs & V2D_LOCKOFS_X) + WM_cursor_modal(window, BC_NS_SCROLLCURSOR); + else if (v2d->keepofs & V2D_LOCKOFS_Y) + WM_cursor_modal(window, BC_EW_SCROLLCURSOR); + else + WM_cursor_modal(window, BC_NSEW_SCROLLCURSOR); + + /* add temp handler */ + WM_event_add_modal_handler(C, &window->handlers, op); + + return OPERATOR_RUNNING_MODAL; +} + +/* handle user input - calculations of mouse-movement need to be done here, not in the apply callback! */ +static int view_pan_modal(bContext *C, wmOperator *op, wmEvent *event) +{ + v2dViewPanData *vpd= op->customdata; + + /* execute the events */ + switch (event->type) { + case MOUSEMOVE: + { + /* calculate new delta transform, then store mouse-coordinates for next-time */ + RNA_int_set(op->ptr, "deltax", (vpd->lastx - event->x)); + RNA_int_set(op->ptr, "deltay", (vpd->lasty - event->y)); + + vpd->lastx= event->x; + vpd->lasty= event->y; + + view_pan_apply(C, op); + } + break; + + case MIDDLEMOUSE: + if (event->val==0) { + /* calculate overall delta mouse-movement for redo */ + RNA_int_set(op->ptr, "deltax", (vpd->startx - vpd->lastx)); + RNA_int_set(op->ptr, "deltay", (vpd->starty - vpd->lasty)); + + view_pan_exit(C, op); + WM_cursor_restore(CTX_wm_window(C)); + + return OPERATOR_FINISHED; + } + break; + } + + return OPERATOR_RUNNING_MODAL; +} + +void VIEW2D_OT_pan(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Pan View"; + ot->idname= "VIEW2D_OT_pan"; + + /* api callbacks */ + ot->exec= view_pan_exec; + ot->invoke= view_pan_invoke; + ot->modal= view_pan_modal; + + /* operator is repeatable */ + ot->flag= OPTYPE_REGISTER; + + /* rna - must keep these in sync with the other operators */ + RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX); + RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX); +} + +/* ------------------ Scrollwheel Versions (2) ---------------------- */ + +/* this operator only needs this single callback, where it callsthe view_pan_*() methods */ +static int view_scrollright_exec(bContext *C, wmOperator *op) +{ + v2dViewPanData *vpd; + + /* initialise default settings (and validate if ok to run) */ + if (!view_pan_init(C, op)) + return OPERATOR_PASS_THROUGH; + + /* also, check if can pan in horizontal axis */ + vpd= op->customdata; + if (vpd->v2d->keepofs & V2D_LOCKOFS_X) { + view_pan_exit(C, op); + return OPERATOR_PASS_THROUGH; + } + + /* set RNA-Props - only movement in positive x-direction */ + RNA_int_set(op->ptr, "deltax", 20); + RNA_int_set(op->ptr, "deltay", 0); + + /* apply movement, then we're done */ + view_pan_apply(C, op); + view_pan_exit(C, op); + + return OPERATOR_FINISHED; +} + +void VIEW2D_OT_scroll_right(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Scroll Right"; + ot->idname= "VIEW2D_OT_scroll_right"; + + /* api callbacks */ + ot->exec= view_scrollright_exec; + + /* operator is repeatable */ + ot->flag= OPTYPE_REGISTER; + + /* rna - must keep these in sync with the other operators */ + RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX); + RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX); +} + + + +/* this operator only needs this single callback, where it callsthe view_pan_*() methods */ +static int view_scrollleft_exec(bContext *C, wmOperator *op) +{ + v2dViewPanData *vpd; + + /* initialise default settings (and validate if ok to run) */ + if (!view_pan_init(C, op)) + return OPERATOR_PASS_THROUGH; + + /* also, check if can pan in horizontal axis */ + vpd= op->customdata; + if (vpd->v2d->keepofs & V2D_LOCKOFS_X) { + view_pan_exit(C, op); + return OPERATOR_PASS_THROUGH; + } + + /* set RNA-Props - only movement in negative x-direction */ + RNA_int_set(op->ptr, "deltax", -20); + RNA_int_set(op->ptr, "deltay", 0); + + /* apply movement, then we're done */ + view_pan_apply(C, op); + view_pan_exit(C, op); + + return OPERATOR_FINISHED; +} + +void VIEW2D_OT_scroll_left(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Scroll Left"; + ot->idname= "VIEW2D_OT_scroll_left"; + + /* api callbacks */ + ot->exec= view_scrollleft_exec; + + /* operator is repeatable */ + ot->flag= OPTYPE_REGISTER; + + /* rna - must keep these in sync with the other operators */ + RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX); + RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX); +} + + +/* this operator only needs this single callback, where it callsthe view_pan_*() methods */ +static int view_scrolldown_exec(bContext *C, wmOperator *op) +{ + v2dViewPanData *vpd; + + /* initialise default settings (and validate if ok to run) */ + if (!view_pan_init(C, op)) + return OPERATOR_PASS_THROUGH; + + /* also, check if can pan in vertical axis */ + vpd= op->customdata; + if (vpd->v2d->keepofs & V2D_LOCKOFS_Y) { + view_pan_exit(C, op); + return OPERATOR_PASS_THROUGH; + } + + /* set RNA-Props */ + RNA_int_set(op->ptr, "deltax", 0); + RNA_int_set(op->ptr, "deltay", -20); + + /* apply movement, then we're done */ + view_pan_apply(C, op); + view_pan_exit(C, op); + + return OPERATOR_FINISHED; +} + +void VIEW2D_OT_scroll_down(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Scroll Down"; + ot->idname= "VIEW2D_OT_scroll_down"; + + /* api callbacks */ + ot->exec= view_scrolldown_exec; + + /* operator is repeatable */ + ot->flag= OPTYPE_REGISTER; + + /* rna - must keep these in sync with the other operators */ + RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX); + RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX); +} + + + +/* this operator only needs this single callback, where it callsthe view_pan_*() methods */ +static int view_scrollup_exec(bContext *C, wmOperator *op) +{ + v2dViewPanData *vpd; + + /* initialise default settings (and validate if ok to run) */ + if (!view_pan_init(C, op)) + return OPERATOR_PASS_THROUGH; + + /* also, check if can pan in vertical axis */ + vpd= op->customdata; + if (vpd->v2d->keepofs & V2D_LOCKOFS_Y) { + view_pan_exit(C, op); + return OPERATOR_PASS_THROUGH; + } + + /* set RNA-Props */ + RNA_int_set(op->ptr, "deltax", 0); + RNA_int_set(op->ptr, "deltay", 20); + + /* apply movement, then we're done */ + view_pan_apply(C, op); + view_pan_exit(C, op); + + return OPERATOR_FINISHED; +} + +void VIEW2D_OT_scroll_up(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Scroll Up"; + ot->idname= "VIEW2D_OT_scroll_up"; + + /* api callbacks */ + ot->exec= view_scrollup_exec; + + /* operator is repeatable */ + ot->flag= OPTYPE_REGISTER; + + /* rna - must keep these in sync with the other operators */ + RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX); + RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX); +} + +/* ********************************************************* */ +/* SINGLE-STEP VIEW ZOOMING OPERATOR */ + +/* This group of operators come in several forms: + * 1) Scrollwheel 'steps' - rolling mousewheel by one step zooms view by predefined amount + * 2) Scrollwheel 'steps' + alt + ctrl/shift - zooms view on one axis only (ctrl=x, shift=y) // XXX this could be implemented... + * 3) Pad +/- Keys - pressing each key moves the zooms the view by a predefined amount + * + * In order to make sure this works, each operator must define the following RNA-Operator Props: + * zoomfacx, zoomfacy - These two zoom factors allow for non-uniform scaling. + * It is safe to scale by 0, as these factors are used to determine + * amount to enlarge 'cur' by + */ + +/* ------------------ 'Shared' stuff ------------------------ */ + +/* check if step-zoom can be applied */ +static short view_zoomstep_ok(bContext *C) +{ + ARegion *ar= CTX_wm_region(C); + View2D *v2d; + + /* check if there's a region in context to work with */ + if (ar == NULL) + return 0; + v2d= &ar->v2d; + + /* check that 2d-view is zoomable */ + if ((v2d->keepzoom & V2D_LOCKZOOM_X) && (v2d->keepzoom & V2D_LOCKZOOM_Y)) + return 0; + + /* view is zoomable */ + return 1; +} + +/* apply transform to view (i.e. adjust 'cur' rect) */ +static void view_zoomstep_apply(bContext *C, wmOperator *op) +{ + ARegion *ar= CTX_wm_region(C); + View2D *v2d= &ar->v2d; + float dx, dy; + + /* calculate amount to move view by */ + dx= (v2d->cur.xmax - v2d->cur.xmin) * (float)RNA_float_get(op->ptr, "zoomfacx"); + dy= (v2d->cur.ymax - v2d->cur.ymin) * (float)RNA_float_get(op->ptr, "zoomfacy"); + + /* only resize view on an axis if change is allowed */ + if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0) { + if (v2d->keepofs & V2D_LOCKOFS_X) { + v2d->cur.xmax -= 2*dx; + } + else { + v2d->cur.xmin += dx; + v2d->cur.xmax -= dx; + } + } + if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0) { + if (v2d->keepofs & V2D_LOCKOFS_Y) { + v2d->cur.ymax -= 2*dy; + } + else { + v2d->cur.ymin += dy; + v2d->cur.ymax -= dy; + } + } + + /* validate that view is in valid configuration after this operation */ + UI_view2d_curRect_validate(v2d); + + /* request updates to be done... */ + ED_area_tag_redraw(CTX_wm_area(C)); + UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY); +} + +/* --------------- Individual Operators ------------------- */ + +/* this operator only needs this single callback, where it calls the view_zoom_*() methods */ +static int view_zoomin_exec(bContext *C, wmOperator *op) +{ + /* check that there's an active region, as View2D data resides there */ + if (!view_zoomstep_ok(C)) + return OPERATOR_PASS_THROUGH; + + /* set RNA-Props - zooming in by uniform factor */ + RNA_float_set(op->ptr, "zoomfacx", 0.0375f); + RNA_float_set(op->ptr, "zoomfacy", 0.0375f); + + /* apply movement, then we're done */ + view_zoomstep_apply(C, op); + + return OPERATOR_FINISHED; +} + +void VIEW2D_OT_zoom_in(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Zoom In"; + ot->idname= "VIEW2D_OT_zoom_in"; + + /* api callbacks */ + ot->exec= view_zoomin_exec; + + /* operator is repeatable */ + ot->flag= OPTYPE_REGISTER; + + /* rna - must keep these in sync with the other operators */ + RNA_def_float(ot->srna, "zoomfacx", 0, -FLT_MAX, FLT_MAX, "Zoom Factor X", "", -FLT_MAX, FLT_MAX); + RNA_def_float(ot->srna, "zoomfacy", 0, -FLT_MAX, FLT_MAX, "Zoom Factor Y", "", -FLT_MAX, FLT_MAX); +} + + + +/* this operator only needs this single callback, where it callsthe view_zoom_*() methods */ +static int view_zoomout_exec(bContext *C, wmOperator *op) +{ + /* check that there's an active region, as View2D data resides there */ + if (!view_zoomstep_ok(C)) + return OPERATOR_PASS_THROUGH; + + /* set RNA-Props - zooming in by uniform factor */ + RNA_float_set(op->ptr, "zoomfacx", -0.0375f); + RNA_float_set(op->ptr, "zoomfacy", -0.0375f); + + /* apply movement, then we're done */ + view_zoomstep_apply(C, op); + + return OPERATOR_FINISHED; +} + +void VIEW2D_OT_zoom_out(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Zoom Out"; + ot->idname= "VIEW2D_OT_zoom_out"; + + /* api callbacks */ + ot->exec= view_zoomout_exec; + + /* operator is repeatable */ + ot->flag= OPTYPE_REGISTER; + + /* rna - must keep these in sync with the other operators */ + RNA_def_float(ot->srna, "zoomfacx", 0, -FLT_MAX, FLT_MAX, "Zoom Factor X", "", -FLT_MAX, FLT_MAX); + RNA_def_float(ot->srna, "zoomfacy", 0, -FLT_MAX, FLT_MAX, "Zoom Factor Y", "", -FLT_MAX, FLT_MAX); +} + +/* ********************************************************* */ +/* DRAG-ZOOM OPERATOR */ + +/* MMB Drag - allows non-uniform scaling by dragging mouse + * + * In order to make sure this works, each operator must define the following RNA-Operator Props: + * deltax, deltay - amounts to add to each side of the 'cur' rect + */ + +/* ------------------ Shared 'core' stuff ---------------------- */ + +/* temp customdata for operator */ +typedef struct v2dViewZoomData { + View2D *v2d; /* view2d we're operating in */ + + int lastx, lasty; /* previous x/y values of mouse in window */ + float dx, dy; /* running tally of previous delta values (for obtaining final zoom) */ +} v2dViewZoomData; + +/* initialise panning customdata */ +static int view_zoomdrag_init(bContext *C, wmOperator *op) +{ + ARegion *ar= CTX_wm_region(C); + v2dViewZoomData *vzd; + View2D *v2d; + + /* regions now have v2d-data by default, so check for region */ + if (ar == NULL) + return 0; + v2d= &ar->v2d; + + /* check that 2d-view is zoomable */ + if ((v2d->keepzoom & V2D_LOCKZOOM_X) && (v2d->keepzoom & V2D_LOCKZOOM_Y)) + return 0; + + /* set custom-data for operator */ + vzd= MEM_callocN(sizeof(v2dViewZoomData), "v2dViewZoomData"); + op->customdata= vzd; + + /* set pointers to owners */ + vzd->v2d= v2d; + + return 1; +} + +/* apply transform to view (i.e. adjust 'cur' rect) */ +static void view_zoomdrag_apply(bContext *C, wmOperator *op) +{ + v2dViewZoomData *vzd= op->customdata; + View2D *v2d= vzd->v2d; + float dx, dy; + + /* get amount to move view by */ + dx= RNA_float_get(op->ptr, "deltax"); + dy= RNA_float_get(op->ptr, "deltay"); + + /* only move view on an axis if change is allowed */ + if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0) { + if (v2d->keepofs & V2D_LOCKOFS_X) { + v2d->cur.xmax -= 2*dx; + } + else { + v2d->cur.xmin += dx; + v2d->cur.xmax -= dx; + } + } + if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0) { + if (v2d->keepofs & V2D_LOCKOFS_Y) { + v2d->cur.ymax -= 2*dy; + } + else { + v2d->cur.ymin += dy; + v2d->cur.ymax -= dy; + } + } + + /* validate that view is in valid configuration after this operation */ + UI_view2d_curRect_validate(v2d); + + /* request updates to be done... */ + ED_area_tag_redraw(CTX_wm_area(C)); + UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY); +} + +/* cleanup temp customdata */ +static void view_zoomdrag_exit(bContext *C, wmOperator *op) +{ + if (op->customdata) { + MEM_freeN(op->customdata); + op->customdata= NULL; + } +} + +/* for 'redo' only, with no user input */ +static int view_zoomdrag_exec(bContext *C, wmOperator *op) +{ + if (!view_zoomdrag_init(C, op)) + return OPERATOR_PASS_THROUGH; + + view_zoomdrag_apply(C, op); + view_zoomdrag_exit(C, op); + return OPERATOR_FINISHED; +} + +/* set up modal operator and relevant settings */ +static int view_zoomdrag_invoke(bContext *C, wmOperator *op, wmEvent *event) +{ + wmWindow *window= CTX_wm_window(C); + v2dViewZoomData *vzd; + View2D *v2d; + + /* set up customdata */ + if (!view_zoomdrag_init(C, op)) + return OPERATOR_PASS_THROUGH; + + vzd= op->customdata; + v2d= vzd->v2d; + + /* set initial settings */ + vzd->lastx= event->x; + vzd->lasty= event->y; + RNA_float_set(op->ptr, "deltax", 0); + RNA_float_set(op->ptr, "deltay", 0); + + if (v2d->keepofs & V2D_LOCKOFS_X) + WM_cursor_modal(window, BC_NS_SCROLLCURSOR); + else if (v2d->keepofs & V2D_LOCKOFS_Y) + WM_cursor_modal(window, BC_EW_SCROLLCURSOR); + else + WM_cursor_modal(window, BC_NSEW_SCROLLCURSOR); + + /* add temp handler */ + WM_event_add_modal_handler(C, &window->handlers, op); + + return OPERATOR_RUNNING_MODAL; +} + +/* handle user input - calculations of mouse-movement need to be done here, not in the apply callback! */ +static int view_zoomdrag_modal(bContext *C, wmOperator *op, wmEvent *event) +{ + v2dViewZoomData *vzd= op->customdata; + View2D *v2d= vzd->v2d; + + /* execute the events */ + switch (event->type) { + case MOUSEMOVE: + { + float dx, dy; + + /* calculate new delta transform, based on zooming mode */ + if (U.viewzoom == USER_ZOOM_SCALE) { + /* 'scale' zooming */ + float dist; + + /* x-axis transform */ + dist = (v2d->mask.xmax - v2d->mask.xmin) / 2.0f; + dx= 1.0f - ((float)fabs(vzd->lastx - dist) + 2.0f) / ((float)fabs(event->x - dist) + 2.0f); + dx*= 0.5f * (v2d->cur.xmax - v2d->cur.xmin); + + /* y-axis transform */ + dist = (v2d->mask.ymax - v2d->mask.ymin) / 2.0f; + dy= 1.0f - ((float)fabs(vzd->lasty - dist) + 2.0f) / ((float)fabs(event->y - dist) + 2.0f); + dy*= 0.5f * (v2d->cur.ymax - v2d->cur.ymin); + } + else { + /* 'continuous' or 'dolly' */ + float fac; + + /* x-axis transform */ + fac= 0.01f * (event->x - vzd->lastx); + dx= fac * (v2d->cur.xmax - v2d->cur.xmin); + + /* y-axis transform */ + fac= 0.01f * (event->y - vzd->lasty); + dy= fac * (v2d->cur.ymax - v2d->cur.ymin); + + /* continous zoom shouldn't move that fast... */ + if (U.viewzoom == USER_ZOOM_CONT) { // XXX store this setting as RNA prop? + dx /= 20.0f; + dy /= 20.0f; + } + } + + /* set transform amount, and add current deltas to stored total delta (for redo) */ + RNA_float_set(op->ptr, "deltax", dx); + RNA_float_set(op->ptr, "deltay", dy); + vzd->dx += dx; + vzd->dy += dy; + + /* store mouse coordinates for next time, if not doing continuous zoom + * - continuous zoom only depends on distance of mouse to starting point to determine rate of change + */ + if (U.viewzoom != USER_ZOOM_CONT) { // XXX store this setting as RNA prop? + vzd->lastx= event->x; + vzd->lasty= event->y; + } + + /* apply zooming */ + view_zoomdrag_apply(C, op); + } + break; + + case MIDDLEMOUSE: + if (event->val==0) { + /* for redo, store the overall deltas - need to respect zoom-locks here... */ + if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0) + RNA_float_set(op->ptr, "deltax", vzd->dx); + else + RNA_float_set(op->ptr, "deltax", 0); + + if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0) + RNA_float_set(op->ptr, "deltay", vzd->dy); + else + RNA_float_set(op->ptr, "deltay", 0); + + /* free customdata */ + view_zoomdrag_exit(C, op); + WM_cursor_restore(CTX_wm_window(C)); + + return OPERATOR_FINISHED; + } + break; + } + + return OPERATOR_RUNNING_MODAL; +} + +void VIEW2D_OT_zoom(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Zoom View"; + ot->idname= "VIEW2D_OT_zoom"; + + /* api callbacks */ + ot->exec= view_zoomdrag_exec; + ot->invoke= view_zoomdrag_invoke; + ot->modal= view_zoomdrag_modal; + + /* operator is repeatable */ + ot->flag= OPTYPE_REGISTER; + + /* rna - must keep these in sync with the other operators */ + RNA_def_float(ot->srna, "deltax", 0, -FLT_MAX, FLT_MAX, "Delta X", "", -FLT_MAX, FLT_MAX); + RNA_def_float(ot->srna, "deltay", 0, -FLT_MAX, FLT_MAX, "Delta Y", "", -FLT_MAX, FLT_MAX); +} + +/* ********************************************************* */ +/* BORDER-ZOOM */ + +/* The user defines a rect using standard borderselect tools, and we use this rect to + * define the new zoom-level of the view in the following ways: + * 1) LEFTMOUSE - zoom in to view + * 2) RIGHTMOUSE - zoom out of view + * + * Currently, these key mappings are hardcoded, but it shouldn't be too important to + * have custom keymappings for this... + */ + +static int view_borderzoom_exec(bContext *C, wmOperator *op) +{ + ARegion *ar= CTX_wm_region(C); + View2D *v2d= &ar->v2d; + rctf rect; + int event_type; + + /* convert coordinates of rect to 'tot' rect coordinates */ + UI_view2d_region_to_view(v2d, RNA_int_get(op->ptr, "xmin"), RNA_int_get(op->ptr, "ymin"), &rect.xmin, &rect.ymin); + UI_view2d_region_to_view(v2d, RNA_int_get(op->ptr, "xmax"), RNA_int_get(op->ptr, "ymax"), &rect.xmax, &rect.ymax); + + /* check if zooming in/out view */ + // XXX hardcoded for now! + event_type= RNA_int_get(op->ptr, "event_type"); + + if (event_type == LEFTMOUSE) { + /* zoom in: + * - 'cur' rect will be defined by the coordinates of the border region + * - just set the 'cur' rect to have the same coordinates as the border region + * if zoom is allowed to be changed + */ + if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0) { + v2d->cur.xmin= rect.xmin; + v2d->cur.xmax= rect.xmax; + } + if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0) { + v2d->cur.ymin= rect.ymin; + v2d->cur.ymax= rect.ymax; + } + } + else { + /* zoom out: + * - the current 'cur' rect coordinates are going to end upwhere the 'rect' ones are, + * but the 'cur' rect coordinates will need to be adjusted to take in more of the view + * - calculate zoom factor, and adjust using center-point + */ + float zoom, center, size; + + // TODO: is this zoom factor calculation valid? It seems to produce same results everytime... + if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0) { + size= (v2d->cur.xmax - v2d->cur.xmin); + zoom= size / (rect.xmax - rect.xmin); + center= (v2d->cur.xmax + v2d->cur.xmin) * 0.5f; + + v2d->cur.xmin= center - (size * zoom); + v2d->cur.xmax= center + (size * zoom); + } + if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0) { + size= (v2d->cur.ymax - v2d->cur.ymin); + zoom= size / (rect.ymax - rect.ymin); + center= (v2d->cur.ymax + v2d->cur.ymin) * 0.5f; + + v2d->cur.ymin= center - (size * zoom); + v2d->cur.ymax= center + (size * zoom); + } + } + + /* validate that view is in valid configuration after this operation */ + UI_view2d_curRect_validate(v2d); + + /* request updates to be done... */ + ED_area_tag_redraw(CTX_wm_area(C)); + UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY); + + return OPERATOR_FINISHED; +} + +void VIEW2D_OT_zoom_border(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Zoom to Border"; + ot->idname= "VIEW2D_OT_zoom_border"; + + /* api callbacks */ + ot->invoke= WM_border_select_invoke; + ot->exec= view_borderzoom_exec; + ot->modal= WM_border_select_modal; + + ot->poll= ED_operator_areaactive; + + /* rna */ + RNA_def_int(ot->srna, "event_type", 0, INT_MIN, INT_MAX, "Event Type", "", INT_MIN, INT_MAX); + RNA_def_int(ot->srna, "xmin", 0, INT_MIN, INT_MAX, "X Min", "", INT_MIN, INT_MAX); + RNA_def_int(ot->srna, "xmax", 0, INT_MIN, INT_MAX, "X Max", "", INT_MIN, INT_MAX); + RNA_def_int(ot->srna, "ymin", 0, INT_MIN, INT_MAX, "Y Min", "", INT_MIN, INT_MAX); + RNA_def_int(ot->srna, "ymax", 0, INT_MIN, INT_MAX, "Y Max", "", INT_MIN, INT_MAX); +} + +/* ********************************************************* */ +/* SCROLLERS */ + +/* Scrollers should behave in the following ways, when clicked on with LMB (and dragged): + * 1) 'Handles' on end of 'bubble' - when the axis that the scroller represents is zoomable, + * enlarge 'cur' rect on the relevant side + * 2) 'Bubble'/'bar' - just drag, and bar should move with mouse (view pans opposite) + * + * In order to make sure this works, each operator must define the following RNA-Operator Props: + * deltax, deltay - define how much to move view by (relative to zoom-correction factor) + */ + +/* customdata for scroller-invoke data */ +typedef struct v2dScrollerMove { + View2D *v2d; /* View2D data that this operation affects */ + + short scroller; /* scroller that mouse is in ('h' or 'v') */ + 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?) + + float fac; /* view adjustment factor, based on size of region */ + float delta; /* amount moved by mouse on axis of interest */ + + int lastx, lasty; /* previous mouse coordinates (in screen coordinates) for determining movement */ +} v2dScrollerMove; + + +/* View2DScrollers is typedef'd in UI_view2d.h + * This is a CUT DOWN VERSION of the 'real' version, which is defined in view2d.c, as we only need focus bubble info + * WARNING: the start of this struct must not change, so that it stays in sync with the 'real' version + * For now, we don't need to have a separate (internal) header for structs like this... + */ +struct View2DScrollers { + /* focus bubbles */ + int vert_min, vert_max; /* vertical scrollbar */ + int hor_min, hor_max; /* horizontal scrollbar */ +}; + +/* quick enum for vsm->zone (scroller handles) */ +enum { + SCROLLHANDLE_MIN= -1, + SCROLLHANDLE_BAR, + SCROLLHANDLE_MAX +} eV2DScrollerHandle_Zone; + +/* ------------------------ */ + +/* check if mouse is within scroller handle + * - mouse = relevant mouse coordinate in region space + * - sc_min, sc_max = extents of scroller + * - sh_min, sh_max = positions of scroller handles + */ +static short mouse_in_scroller_handle(int mouse, int sc_min, int sc_max, int sh_min, int sh_max) +{ + short in_min, in_max, in_view=1; + + /* firstly, check if + * - 'bubble' fills entire scroller + * - 'bubble' completely out of view on either side + */ + if ((sh_min <= sc_min) && (sh_max >= sc_max)) in_view= 0; + if (sh_min == sh_max) { + if (sh_min <= sc_min) in_view= 0; + if (sh_max >= sc_max) in_view= 0; + } + else { + if (sh_max <= sc_min) in_view= 0; + if (sh_min >= sc_max) in_view= 0; + } + + + if (in_view == 0) { + /* handles are only activated if the mouse is within the relative quater lengths of the scroller */ + int qLen = (sc_max + sc_min) / 4; + + if (mouse >= (sc_max - qLen)) + return SCROLLHANDLE_MAX; + else if (mouse <= qLen) + return SCROLLHANDLE_MIN; + else + return SCROLLHANDLE_BAR; + } + + /* check if mouse is in or past either handle */ + in_max= ( (mouse >= (sh_max - V2D_SCROLLER_HANDLE_SIZE)) && (mouse <= (sh_max + V2D_SCROLLER_HANDLE_SIZE)) ); + in_min= ( (mouse <= (sh_min + V2D_SCROLLER_HANDLE_SIZE)) && (mouse >= (sh_min - V2D_SCROLLER_HANDLE_SIZE)) ); + + /* check if overlap --> which means user clicked on bar, as bar is within handles region */ + if (in_max && in_min) + return SCROLLHANDLE_BAR; + else if (in_max) + return SCROLLHANDLE_MAX; + else if (in_min) + return SCROLLHANDLE_MIN; + + /* unlikely to happen, though we just cover it in case */ + return SCROLLHANDLE_BAR; +} + +/* initialise customdata for scroller manipulation operator */ +static void scroller_activate_init(bContext *C, wmOperator *op, wmEvent *event, short in_scroller) +{ + v2dScrollerMove *vsm; + View2DScrollers *scrollers; + ARegion *ar= CTX_wm_region(C); + View2D *v2d= &ar->v2d; + float mask_size; + int x, y; + + /* set custom-data for operator */ + vsm= MEM_callocN(sizeof(v2dScrollerMove), "v2dScrollerMove"); + op->customdata= vsm; + + /* set general data */ + vsm->v2d= v2d; + vsm->scroller= in_scroller; + + /* store mouse-coordinates, and convert mouse/screen coordinates to region coordinates */ + vsm->lastx = event->x; + vsm->lasty = event->y; + x= event->x - ar->winrct.xmin; + y= event->y - ar->winrct.ymin; + + /* 'zone' depends on where mouse is relative to bubble + * - zooming must be allowed on this axis, otherwise, default to pan + */ + scrollers= UI_view2d_scrollers_calc(C, v2d, V2D_ARG_DUMMY, V2D_ARG_DUMMY, V2D_ARG_DUMMY, V2D_ARG_DUMMY); + if (in_scroller == 'h') { + /* horizontal scroller - calculate adjustment factor first */ + mask_size= (float)(v2d->hor.xmax - v2d->hor.xmin); + vsm->fac= (v2d->tot.xmax - v2d->tot.xmin) / mask_size; + + /* get 'zone' (i.e. which part of scroller is activated) */ + if (v2d->keepzoom & V2D_LOCKZOOM_X) { + /* default to scroll, as handles not usable */ + vsm->zone= SCROLLHANDLE_BAR; + } + else { + /* check which handle we're in */ + vsm->zone= mouse_in_scroller_handle(x, v2d->hor.xmin, v2d->hor.xmax, scrollers->hor_min, scrollers->hor_max); + } + } + else { + /* vertical scroller - calculate adjustment factor first */ + mask_size= (float)(v2d->vert.ymax - v2d->vert.ymin); + vsm->fac= (v2d->tot.ymax - v2d->tot.ymin) / mask_size; + + /* get 'zone' (i.e. which part of scroller is activated) */ + if (v2d->keepzoom & V2D_LOCKZOOM_Y) { + /* default to scroll, as handles not usable */ + vsm->zone= SCROLLHANDLE_BAR; + } + else { + /* check which handle we're in */ + vsm->zone= mouse_in_scroller_handle(y, v2d->vert.ymin, v2d->vert.ymax, scrollers->vert_min, scrollers->vert_max); + } + } + UI_view2d_scrollers_free(scrollers); +} + +/* cleanup temp customdata */ +static void scroller_activate_exit(bContext *C, wmOperator *op) +{ + if (op->customdata) { + MEM_freeN(op->customdata); + op->customdata= NULL; + } +} + +/* apply transform to view (i.e. adjust 'cur' rect) */ +static void scroller_activate_apply(bContext *C, wmOperator *op) +{ + v2dScrollerMove *vsm= op->customdata; + View2D *v2d= vsm->v2d; + float temp; + + /* calculate amount to move view by */ + temp= vsm->fac * vsm->delta; + + /* type of movement */ + switch (vsm->zone) { + case SCROLLHANDLE_MIN: + /* only expand view on axis if zoom is allowed */ + if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X)) + v2d->cur.xmin -= temp; + if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y)) + v2d->cur.ymin -= temp; + break; + + case SCROLLHANDLE_MAX: + /* only expand view on axis if zoom is allowed */ + if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X)) + v2d->cur.xmax += temp; + if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y)) + v2d->cur.ymax += temp; + break; + + default: /* SCROLLHANDLE_BAR */ + /* only move view on an axis if panning is allowed */ + if ((vsm->scroller == 'h') && !(v2d->keepofs & V2D_LOCKOFS_X)) { + v2d->cur.xmin += temp; + v2d->cur.xmax += temp; + } + if ((vsm->scroller == 'v') && !(v2d->keepofs & V2D_LOCKOFS_Y)) { + v2d->cur.ymin += temp; + v2d->cur.ymax += temp; + } + break; + } + + /* validate that view is in valid configuration after this operation */ + UI_view2d_curRect_validate(v2d); + + /* request updates to be done... */ + ED_area_tag_redraw(CTX_wm_area(C)); + UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY); +} + +/* handle user input for scrollers - calculations of mouse-movement need to be done here, not in the apply callback! */ +static int scroller_activate_modal(bContext *C, wmOperator *op, wmEvent *event) +{ + v2dScrollerMove *vsm= op->customdata; + + /* execute the events */ + switch (event->type) { + case MOUSEMOVE: + { + /* calculate new delta transform, then store mouse-coordinates for next-time */ + if (vsm->zone != SCROLLHANDLE_MIN) { + /* if using bar (i.e. 'panning') or 'max' zoom widget */ + switch (vsm->scroller) { + case 'h': /* horizontal scroller - so only horizontal movement ('cur' moves opposite to mouse) */ + vsm->delta= (float)(event->x - vsm->lastx); + break; + case 'v': /* vertical scroller - so only vertical movement ('cur' moves opposite to mouse) */ + vsm->delta= (float)(event->y - vsm->lasty); + break; + } + } + else { + /* using 'min' zoom widget */ + switch (vsm->scroller) { + case 'h': /* horizontal scroller - so only horizontal movement ('cur' moves with mouse) */ + vsm->delta= (float)(vsm->lastx - event->x); + break; + case 'v': /* vertical scroller - so only vertical movement ('cur' moves with to mouse) */ + vsm->delta= (float)(vsm->lasty - event->y); + break; + } + } + + /* store previous coordinates */ + vsm->lastx= event->x; + vsm->lasty= event->y; + + scroller_activate_apply(C, op); + } + break; + + case LEFTMOUSE: + if (event->val==0) { + scroller_activate_exit(C, op); + return OPERATOR_FINISHED; + } + break; + } + + return OPERATOR_RUNNING_MODAL; +} + + +/* a click (or click drag in progress) should have occurred, so check if it happened in scrollbar */ +static int scroller_activate_invoke(bContext *C, wmOperator *op, wmEvent *event) +{ + ARegion *ar= CTX_wm_region(C); + View2D *v2d= &ar->v2d; + short in_scroller= 0; + + /* check if mouse in scrollbars, if they're enabled */ + in_scroller= UI_view2d_mouse_in_scrollers(C, v2d, event->x, event->y); + + /* if in a scroller, init customdata then set modal handler which will catch mousedown to start doing useful stuff */ + if (in_scroller) { + v2dScrollerMove *vsm; + + /* initialise customdata */ + scroller_activate_init(C, op, event, in_scroller); + vsm= (v2dScrollerMove *)op->customdata; + + /* check if zone is inappropriate (i.e. 'bar' but panning is banned), so cannot continue */ + if (vsm->zone == SCROLLHANDLE_BAR) { + if ( ((vsm->scroller=='h') && (v2d->keepofs & V2D_LOCKOFS_X)) || + ((vsm->scroller=='v') && (v2d->keepofs & V2D_LOCKOFS_Y)) ) + { + /* free customdata initialised */ + scroller_activate_exit(C, op); + + /* can't catch this event for ourselves, so let it go to someone else? */ + return OPERATOR_PASS_THROUGH; + } + } + + /* still ok, so can add */ + WM_event_add_modal_handler(C, &CTX_wm_window(C)->handlers, op); + return OPERATOR_RUNNING_MODAL; + } + else { + /* not in scroller, so nothing happened... (pass through let's something else catch event) */ + return OPERATOR_PASS_THROUGH; + } +} + +/* LMB-Drag in Scrollers - not repeatable operator! */ +void VIEW2D_OT_scroller_activate(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Scroller Activate"; + ot->idname= "VIEW2D_OT_scroller_activate"; + + /* api callbacks */ + ot->invoke= scroller_activate_invoke; + ot->modal= scroller_activate_modal; + ot->poll= view2d_poll; +} + +/* ********************************************************* */ +/* RESET */ + +static int reset_exec(bContext *C, wmOperator *op) +{ + ARegion *ar= CTX_wm_region(C); + View2D *v2d= &ar->v2d; + int winx, winy; + + /* zoom 1.0 */ + winx= (float)(v2d->mask.xmax - v2d->mask.xmin + 1); + winy= (float)(v2d->mask.ymax - v2d->mask.ymin + 1); + + v2d->cur.xmax= v2d->cur.xmin + winx; + v2d->cur.ymax= v2d->cur.ymin + winy; + + /* align */ + if(v2d->align) { + /* posx and negx flags are mutually exclusive, so watch out */ + if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) { + v2d->cur.xmax= 0.0f; + v2d->cur.xmin= v2d->winx; + } + else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) { + v2d->cur.xmax= v2d->cur.xmax - v2d->cur.xmin; + v2d->cur.xmin= 0.0f; + } + + /* - posx and negx flags are mutually exclusive, so watch out */ + if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) { + v2d->cur.ymax= 0.0f; + v2d->cur.ymin= -v2d->winy; + } + else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) { + v2d->cur.ymax= v2d->cur.ymax - v2d->cur.ymin; + v2d->cur.ymin= 0.0f; + } + } + + /* validate that view is in valid configuration after this operation */ + UI_view2d_curRect_validate(v2d); + + /* request updates to be done... */ + ED_area_tag_redraw(CTX_wm_area(C)); + UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY); + + return OPERATOR_FINISHED; +} + +void VIEW2D_OT_reset(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Reset View"; + ot->idname= "VIEW2D_OT_reset"; + + /* api callbacks */ + ot->exec= reset_exec; + ot->poll= view2d_poll; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; +} + +/* ********************************************************* */ +/* Registration */ + +void ui_view2d_operatortypes(void) +{ + WM_operatortype_append(VIEW2D_OT_pan); + + WM_operatortype_append(VIEW2D_OT_scroll_left); + WM_operatortype_append(VIEW2D_OT_scroll_right); + WM_operatortype_append(VIEW2D_OT_scroll_up); + WM_operatortype_append(VIEW2D_OT_scroll_down); + + WM_operatortype_append(VIEW2D_OT_zoom_in); + WM_operatortype_append(VIEW2D_OT_zoom_out); + + WM_operatortype_append(VIEW2D_OT_zoom); + WM_operatortype_append(VIEW2D_OT_zoom_border); + + WM_operatortype_append(VIEW2D_OT_scroller_activate); + + WM_operatortype_append(VIEW2D_OT_reset); +} + +void UI_view2d_keymap(wmWindowManager *wm) +{ + ListBase *keymap= WM_keymap_listbase(wm, "View2D", 0, 0); + + /* pan/scroll */ + WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MIDDLEMOUSE, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MIDDLEMOUSE, KM_PRESS, KM_SHIFT, 0); + + WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_right", WHEELDOWNMOUSE, KM_PRESS, KM_CTRL, 0); + WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_left", WHEELUPMOUSE, KM_PRESS, KM_CTRL, 0); + + WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_down", WHEELDOWNMOUSE, KM_PRESS, KM_SHIFT, 0); + WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_up", WHEELUPMOUSE, KM_PRESS, KM_SHIFT, 0); + + /* zoom - single step */ + WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_out", WHEELOUTMOUSE, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_in", WHEELINMOUSE, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_out", PADMINUS, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_in", PADPLUSKEY, KM_PRESS, 0, 0); + + /* scroll up/down - no modifiers, only when zoom fails */ + /* these may fail if zoom is disallowed, in which case they should pass on event */ + WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_down", WHEELDOWNMOUSE, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_up", WHEELUPMOUSE, KM_PRESS, 0, 0); + /* these may be necessary if vertical scroll is disallowed */ + WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_right", WHEELDOWNMOUSE, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_left", WHEELUPMOUSE, KM_PRESS, 0, 0); + + /* zoom - drag */ + WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MIDDLEMOUSE, KM_PRESS, KM_CTRL, 0); + + /* borderzoom - drag */ + WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_border", BKEY, KM_PRESS, KM_SHIFT, 0); + + /* scrollers */ + WM_keymap_add_item(keymap, "VIEW2D_OT_scroller_activate", LEFTMOUSE, KM_PRESS, 0, 0); + + /* Alternative keymap for buttons listview */ + keymap= WM_keymap_listbase(wm, "View2D Buttons List", 0, 0); + WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MIDDLEMOUSE, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_down", WHEELDOWNMOUSE, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_up", WHEELUPMOUSE, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MIDDLEMOUSE, KM_PRESS, KM_CTRL, 0); + WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_out", PADMINUS, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_in", PADPLUSKEY, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "VIEW2D_OT_reset", HOMEKEY, KM_PRESS, 0, 0); +} + |