801 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			801 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * ***** 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 | 
						|
 *
 | 
						|
 * The Original Code is Copyright (C) 2008 Blender Foundation.
 | 
						|
 * All rights reserved.
 | 
						|
 *
 | 
						|
 * Contributor(s): Blender Foundation
 | 
						|
 *
 | 
						|
 * ***** END GPL LICENSE BLOCK *****
 | 
						|
 */
 | 
						|
 | 
						|
/** \file blender/editors/interface/interface_region_search.c
 | 
						|
 *  \ingroup edinterface
 | 
						|
 *
 | 
						|
 * Search Box Region & Interaction
 | 
						|
 */
 | 
						|
 | 
						|
#include <stdarg.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
#include <assert.h>
 | 
						|
 | 
						|
#include "MEM_guardedalloc.h"
 | 
						|
 | 
						|
#include "DNA_userdef_types.h"
 | 
						|
 | 
						|
#include "BLI_math.h"
 | 
						|
 | 
						|
#include "BLI_string.h"
 | 
						|
#include "BLI_rect.h"
 | 
						|
#include "BLI_utildefines.h"
 | 
						|
 | 
						|
#include "BKE_context.h"
 | 
						|
#include "BKE_screen.h"
 | 
						|
 | 
						|
#include "WM_api.h"
 | 
						|
#include "WM_types.h"
 | 
						|
 | 
						|
#include "RNA_access.h"
 | 
						|
 | 
						|
#include "BIF_gl.h"
 | 
						|
 | 
						|
#include "UI_interface.h"
 | 
						|
#include "UI_interface_icons.h"
 | 
						|
#include "UI_view2d.h"
 | 
						|
 | 
						|
#include "BLT_translation.h"
 | 
						|
 | 
						|
#include "ED_screen.h"
 | 
						|
 | 
						|
#include "interface_intern.h"
 | 
						|
#include "interface_regions_intern.h"
 | 
						|
 | 
						|
#define MENU_BORDER			(int)(0.3f * U.widget_unit)
 | 
						|
 | 
						|
/* -------------------------------------------------------------------- */
 | 
						|
/** \name Search Box Creation
 | 
						|
 * \{ */
 | 
						|
 | 
						|
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;
 | 
						|
	int *icons;
 | 
						|
 | 
						|
	AutoComplete *autocpl;
 | 
						|
	void *active;
 | 
						|
};
 | 
						|
 | 
						|
typedef struct uiSearchboxData {
 | 
						|
	rcti bbox;
 | 
						|
	uiFontStyle fstyle;
 | 
						|
	uiSearchItems items;
 | 
						|
	int active;     /* index in items array */
 | 
						|
	bool noback;    /* when menu opened with enough space for this */
 | 
						|
	bool preview;   /* draw thumbnail previews, rather than list */
 | 
						|
	bool use_sep;   /* use the UI_SEP_CHAR char for splitting shortcuts (good for operators, bad for data) */
 | 
						|
	int prv_rows, prv_cols;
 | 
						|
} uiSearchboxData;
 | 
						|
 | 
						|
#define SEARCH_ITEMS    10
 | 
						|
 | 
						|
/* exported for use by search callbacks */
 | 
						|
/* returns zero if nothing to add */
 | 
						|
bool UI_search_item_add(uiSearchItems *items, const char *name, void *poin, int iconid)
 | 
						|
{
 | 
						|
	/* hijack for autocomplete */
 | 
						|
	if (items->autocpl) {
 | 
						|
		UI_autocomplete_update_name(items->autocpl, name);
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	/* hijack for finding active item */
 | 
						|
	if (items->active) {
 | 
						|
		if (poin == items->active)
 | 
						|
			items->offset_i = items->totitem;
 | 
						|
		items->totitem++;
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	if (items->totitem >= items->maxitem) {
 | 
						|
		items->more = 1;
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	/* skip first items in list */
 | 
						|
	if (items->offset_i > 0) {
 | 
						|
		items->offset_i--;
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	if (items->names)
 | 
						|
		BLI_strncpy(items->names[items->totitem], name, items->maxstrlen);
 | 
						|
	if (items->pointers)
 | 
						|
		items->pointers[items->totitem] = poin;
 | 
						|
	if (items->icons)
 | 
						|
		items->icons[items->totitem] = iconid;
 | 
						|
 | 
						|
	items->totitem++;
 | 
						|
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
int UI_searchbox_size_y(void)
 | 
						|
{
 | 
						|
	return SEARCH_ITEMS * UI_UNIT_Y + 2 * UI_POPUP_MENU_TOP;
 | 
						|
}
 | 
						|
 | 
						|
int UI_searchbox_size_x(void)
 | 
						|
{
 | 
						|
	return 12 * UI_UNIT_X;
 | 
						|
}
 | 
						|
 | 
						|
int UI_search_items_find_index(uiSearchItems *items, const char *name)
 | 
						|
{
 | 
						|
	int i;
 | 
						|
	for (i = 0; i < items->totitem; i++) {
 | 
						|
		if (STREQ(name, items->names[i])) {
 | 
						|
			return i;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return -1;
 | 
						|
}
 | 
						|
 | 
						|
/* 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 = -1;
 | 
						|
	}
 | 
						|
	else if (data->active >= data->items.totitem) {
 | 
						|
		if (data->items.more) {
 | 
						|
			data->items.offset++;
 | 
						|
			data->active = data->items.totitem - 1;
 | 
						|
			ui_searchbox_update(C, ar, but, false);
 | 
						|
		}
 | 
						|
		else {
 | 
						|
			data->active = data->items.totitem - 1;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	else if (data->active < 0) {
 | 
						|
		if (data->items.offset) {
 | 
						|
			data->items.offset--;
 | 
						|
			data->active = 0;
 | 
						|
			ui_searchbox_update(C, ar, but, false);
 | 
						|
		}
 | 
						|
		else {
 | 
						|
			/* only let users step into an 'unset' state for unlink buttons */
 | 
						|
			data->active = (but->flag & UI_BUT_VALUE_CLEAR) ? -1 : 0;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	ED_region_tag_redraw(ar);
 | 
						|
}
 | 
						|
 | 
						|
static void ui_searchbox_butrect(rcti *r_rect, uiSearchboxData *data, int itemnr)
 | 
						|
{
 | 
						|
	/* thumbnail preview */
 | 
						|
	if (data->preview) {
 | 
						|
		int butw = (BLI_rcti_size_x(&data->bbox) - 2 * MENU_BORDER) / data->prv_cols;
 | 
						|
		int buth = (BLI_rcti_size_y(&data->bbox) - 2 * MENU_BORDER) / data->prv_rows;
 | 
						|
		int row, col;
 | 
						|
 | 
						|
		*r_rect = data->bbox;
 | 
						|
 | 
						|
		col = itemnr % data->prv_cols;
 | 
						|
		row = itemnr / data->prv_cols;
 | 
						|
 | 
						|
		r_rect->xmin += MENU_BORDER + (col * butw);
 | 
						|
		r_rect->xmax = r_rect->xmin + butw;
 | 
						|
 | 
						|
		r_rect->ymax -= MENU_BORDER + (row * buth);
 | 
						|
		r_rect->ymin = r_rect->ymax - buth;
 | 
						|
	}
 | 
						|
	/* list view */
 | 
						|
	else {
 | 
						|
		int buth = (BLI_rcti_size_y(&data->bbox) - 2 * UI_POPUP_MENU_TOP) / SEARCH_ITEMS;
 | 
						|
 | 
						|
		*r_rect = data->bbox;
 | 
						|
		r_rect->xmin = data->bbox.xmin + 3.0f;
 | 
						|
		r_rect->xmax = data->bbox.xmax - 3.0f;
 | 
						|
 | 
						|
		r_rect->ymax = data->bbox.ymax - UI_POPUP_MENU_TOP - itemnr * buth;
 | 
						|
		r_rect->ymin = r_rect->ymax - buth;
 | 
						|
	}
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
int ui_searchbox_find_index(ARegion *ar, const char *name)
 | 
						|
{
 | 
						|
	uiSearchboxData *data = ar->regiondata;
 | 
						|
	return UI_search_items_find_index(&data->items, name);
 | 
						|
}
 | 
						|
 | 
						|
/* x and y in screencoords */
 | 
						|
bool ui_searchbox_inside(ARegion *ar, int x, int y)
 | 
						|
{
 | 
						|
	uiSearchboxData *data = ar->regiondata;
 | 
						|
 | 
						|
	return BLI_rcti_isect_pt(&data->bbox, x - ar->winrct.xmin, y - ar->winrct.ymin);
 | 
						|
}
 | 
						|
 | 
						|
/* string validated to be of correct length (but->hardmax) */
 | 
						|
bool ui_searchbox_apply(uiBut *but, ARegion *ar)
 | 
						|
{
 | 
						|
	uiSearchboxData *data = ar->regiondata;
 | 
						|
 | 
						|
	but->func_arg2 = NULL;
 | 
						|
 | 
						|
	if (data->active != -1) {
 | 
						|
		const char *name = data->items.names[data->active];
 | 
						|
		const char *name_sep = data->use_sep ? strrchr(name, UI_SEP_CHAR) : NULL;
 | 
						|
 | 
						|
		BLI_strncpy(but->editstr, name, name_sep ? (name_sep - name) : data->items.maxstrlen);
 | 
						|
 | 
						|
		but->func_arg2 = data->items.pointers[data->active];
 | 
						|
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
	else if (but->flag & UI_BUT_VALUE_CLEAR) {
 | 
						|
		/* It is valid for _VALUE_CLEAR flavor to have no active element (it's a valid way to unlink). */
 | 
						|
		but->editstr[0] = '\0';
 | 
						|
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void ui_searchbox_event(bContext *C, ARegion *ar, uiBut *but, const wmEvent *event)
 | 
						|
{
 | 
						|
	uiSearchboxData *data = ar->regiondata;
 | 
						|
	int type = event->type, val = event->val;
 | 
						|
 | 
						|
	if (type == MOUSEPAN)
 | 
						|
		ui_pan_to_scroll(event, &type, &val);
 | 
						|
 | 
						|
	switch (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_rcti_isect_pt(&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_rcti_isect_pt(&rect, event->x - ar->winrct.xmin, event->y - ar->winrct.ymin)) {
 | 
						|
						if (data->active != a) {
 | 
						|
							data->active = a;
 | 
						|
							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, const bool reset)
 | 
						|
{
 | 
						|
	uiSearchboxData *data = ar->regiondata;
 | 
						|
 | 
						|
	/* reset vars */
 | 
						|
	data->items.totitem = 0;
 | 
						|
	data->items.more = 0;
 | 
						|
	if (reset == false) {
 | 
						|
		data->items.offset_i = data->items.offset;
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		data->items.offset_i = data->items.offset = 0;
 | 
						|
		data->active = -1;
 | 
						|
 | 
						|
		/* handle active */
 | 
						|
		if (but->search_func && but->func_arg2) {
 | 
						|
			data->items.active = but->func_arg2;
 | 
						|
			but->search_func(C, but->search_arg, but->editstr, &data->items);
 | 
						|
			data->items.active = NULL;
 | 
						|
 | 
						|
			/* found active item, calculate real offset by centering it */
 | 
						|
			if (data->items.totitem) {
 | 
						|
				/* first case, begin of list */
 | 
						|
				if (data->items.offset_i < data->items.maxitem) {
 | 
						|
					data->active = data->items.offset_i;
 | 
						|
					data->items.offset_i = 0;
 | 
						|
				}
 | 
						|
				else {
 | 
						|
					/* second case, end of list */
 | 
						|
					if (data->items.totitem - data->items.offset_i <= data->items.maxitem) {
 | 
						|
						data->active = data->items.offset_i - data->items.totitem + data->items.maxitem;
 | 
						|
						data->items.offset_i = data->items.totitem - data->items.maxitem;
 | 
						|
					}
 | 
						|
					else {
 | 
						|
						/* center active item */
 | 
						|
						data->items.offset_i -= data->items.maxitem / 2;
 | 
						|
						data->active = data->items.maxitem / 2;
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
			data->items.offset = data->items.offset_i;
 | 
						|
			data->items.totitem = 0;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/* callback */
 | 
						|
	if (but->search_func)
 | 
						|
		but->search_func(C, but->search_arg, but->editstr, &data->items);
 | 
						|
 | 
						|
	/* handle case where editstr is equal to one of items */
 | 
						|
	if (reset && data->active == -1) {
 | 
						|
		int a;
 | 
						|
 | 
						|
		for (a = 0; a < data->items.totitem; a++) {
 | 
						|
			const char *name = data->items.names[a];
 | 
						|
			const char *name_sep = data->use_sep ? strrchr(name, UI_SEP_CHAR) : NULL;
 | 
						|
			if (STREQLEN(but->editstr, name, name_sep ? (name_sep - name) : data->items.maxstrlen)) {
 | 
						|
				data->active = a;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (data->items.totitem == 1 && but->editstr[0])
 | 
						|
			data->active = 0;
 | 
						|
	}
 | 
						|
 | 
						|
	/* validate selected item */
 | 
						|
	ui_searchbox_select(C, ar, but, 0);
 | 
						|
 | 
						|
	ED_region_tag_redraw(ar);
 | 
						|
}
 | 
						|
 | 
						|
int ui_searchbox_autocomplete(bContext *C, ARegion *ar, uiBut *but, char *str)
 | 
						|
{
 | 
						|
	uiSearchboxData *data = ar->regiondata;
 | 
						|
	int match = AUTOCOMPLETE_NO_MATCH;
 | 
						|
 | 
						|
	if (str[0]) {
 | 
						|
		data->items.autocpl = UI_autocomplete_begin(str, ui_but_string_get_max_length(but));
 | 
						|
 | 
						|
		but->search_func(C, but->search_arg, but->editstr, &data->items);
 | 
						|
 | 
						|
		match = UI_autocomplete_end(data->items.autocpl, str);
 | 
						|
		data->items.autocpl = NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	return match;
 | 
						|
}
 | 
						|
 | 
						|
static void ui_searchbox_region_draw_cb(const bContext *UNUSED(C), ARegion *ar)
 | 
						|
{
 | 
						|
	uiSearchboxData *data = ar->regiondata;
 | 
						|
 | 
						|
	/* pixel space */
 | 
						|
	wmOrtho2_region_pixelspace(ar);
 | 
						|
 | 
						|
	if (data->noback == false)
 | 
						|
		ui_draw_search_back(NULL, NULL, &data->bbox);  /* style not used yet */
 | 
						|
 | 
						|
	/* draw text */
 | 
						|
	if (data->items.totitem) {
 | 
						|
		rcti rect;
 | 
						|
		int a;
 | 
						|
 | 
						|
		if (data->preview) {
 | 
						|
			/* draw items */
 | 
						|
			for (a = 0; a < data->items.totitem; a++) {
 | 
						|
				ui_searchbox_butrect(&rect, data, a);
 | 
						|
 | 
						|
				/* widget itself */
 | 
						|
				ui_draw_preview_item(&data->fstyle, &rect, data->items.names[a], data->items.icons[a],
 | 
						|
				                     (a == data->active) ? UI_ACTIVE : 0);
 | 
						|
			}
 | 
						|
 | 
						|
			/* indicate more */
 | 
						|
			if (data->items.more) {
 | 
						|
				ui_searchbox_butrect(&rect, data, data->items.maxitem - 1);
 | 
						|
				glEnable(GL_BLEND);
 | 
						|
				UI_icon_draw(rect.xmax - 18, rect.ymin - 7, ICON_TRIA_DOWN);
 | 
						|
				glDisable(GL_BLEND);
 | 
						|
			}
 | 
						|
			if (data->items.offset) {
 | 
						|
				ui_searchbox_butrect(&rect, data, 0);
 | 
						|
				glEnable(GL_BLEND);
 | 
						|
				UI_icon_draw(rect.xmin, rect.ymax - 9, ICON_TRIA_UP);
 | 
						|
				glDisable(GL_BLEND);
 | 
						|
			}
 | 
						|
 | 
						|
		}
 | 
						|
		else {
 | 
						|
			/* draw items */
 | 
						|
			for (a = 0; a < data->items.totitem; a++) {
 | 
						|
				ui_searchbox_butrect(&rect, data, a);
 | 
						|
 | 
						|
				/* widget itself */
 | 
						|
				ui_draw_menu_item(&data->fstyle, &rect, data->items.names[a], data->items.icons[a],
 | 
						|
				                  (a == data->active) ? UI_ACTIVE : 0, data->use_sep);
 | 
						|
 | 
						|
			}
 | 
						|
			/* indicate more */
 | 
						|
			if (data->items.more) {
 | 
						|
				ui_searchbox_butrect(&rect, data, data->items.maxitem - 1);
 | 
						|
				glEnable(GL_BLEND);
 | 
						|
				UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymin - 9, ICON_TRIA_DOWN);
 | 
						|
				glDisable(GL_BLEND);
 | 
						|
			}
 | 
						|
			if (data->items.offset) {
 | 
						|
				ui_searchbox_butrect(&rect, data, 0);
 | 
						|
				glEnable(GL_BLEND);
 | 
						|
				UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymax - 7, ICON_TRIA_UP);
 | 
						|
				glDisable(GL_BLEND);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void ui_searchbox_region_free_cb(ARegion *ar)
 | 
						|
{
 | 
						|
	uiSearchboxData *data = ar->regiondata;
 | 
						|
	int a;
 | 
						|
 | 
						|
	/* free search data */
 | 
						|
	for (a = 0; a < data->items.maxitem; a++) {
 | 
						|
		MEM_freeN(data->items.names[a]);
 | 
						|
	}
 | 
						|
	MEM_freeN(data->items.names);
 | 
						|
	MEM_freeN(data->items.pointers);
 | 
						|
	MEM_freeN(data->items.icons);
 | 
						|
 | 
						|
	MEM_freeN(data);
 | 
						|
	ar->regiondata = NULL;
 | 
						|
}
 | 
						|
 | 
						|
ARegion *ui_searchbox_create_generic(bContext *C, ARegion *butregion, uiBut *but)
 | 
						|
{
 | 
						|
	wmWindow *win = CTX_wm_window(C);
 | 
						|
	uiStyle *style = UI_style_get();
 | 
						|
	static ARegionType type;
 | 
						|
	ARegion *ar;
 | 
						|
	uiSearchboxData *data;
 | 
						|
	float aspect = but->block->aspect;
 | 
						|
	rctf rect_fl;
 | 
						|
	rcti rect_i;
 | 
						|
	const int margin = UI_POPUP_MARGIN;
 | 
						|
	int winx /*, winy */, ofsx, ofsy;
 | 
						|
	int i;
 | 
						|
 | 
						|
	/* create area region */
 | 
						|
	ar = ui_region_temp_add(CTX_wm_screen(C));
 | 
						|
 | 
						|
	memset(&type, 0, sizeof(ARegionType));
 | 
						|
	type.draw = ui_searchbox_region_draw_cb;
 | 
						|
	type.free = ui_searchbox_region_free_cb;
 | 
						|
	type.regionid = RGN_TYPE_TEMPORARY;
 | 
						|
	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);
 | 
						|
	UI_fontstyle_set(&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_SEARCH_MENU)
 | 
						|
		data->noback = true;
 | 
						|
 | 
						|
	if (but->a1 > 0 && but->a2 > 0) {
 | 
						|
		data->preview = true;
 | 
						|
		data->prv_rows = but->a1;
 | 
						|
		data->prv_cols = but->a2;
 | 
						|
	}
 | 
						|
 | 
						|
	/* only show key shortcuts when needed (not rna buttons) [#36699] */
 | 
						|
	if (but->rnaprop == NULL) {
 | 
						|
		data->use_sep = true;
 | 
						|
	}
 | 
						|
 | 
						|
	/* compute position */
 | 
						|
	if (but->block->flag & UI_BLOCK_SEARCH_MENU) {
 | 
						|
		const int search_but_h = BLI_rctf_size_y(&but->rect) + 10;
 | 
						|
		/* this case is search menu inside other menu */
 | 
						|
		/* we copy region size */
 | 
						|
 | 
						|
		ar->winrct = butregion->winrct;
 | 
						|
 | 
						|
		/* widget rect, in region coords */
 | 
						|
		data->bbox.xmin = margin;
 | 
						|
		data->bbox.xmax = BLI_rcti_size_x(&ar->winrct) - margin;
 | 
						|
		data->bbox.ymin = margin;
 | 
						|
		data->bbox.ymax = BLI_rcti_size_y(&ar->winrct) - margin;
 | 
						|
 | 
						|
		/* check if button is lower half */
 | 
						|
		if (but->rect.ymax < BLI_rctf_cent_y(&but->block->rect)) {
 | 
						|
			data->bbox.ymin += search_but_h;
 | 
						|
		}
 | 
						|
		else {
 | 
						|
			data->bbox.ymax -= search_but_h;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		const int searchbox_width = UI_searchbox_size_x();
 | 
						|
 | 
						|
		rect_fl.xmin = but->rect.xmin - 5;   /* align text with button */
 | 
						|
		rect_fl.xmax = but->rect.xmax + 5;   /* symmetrical */
 | 
						|
		rect_fl.ymax = but->rect.ymin;
 | 
						|
		rect_fl.ymin = rect_fl.ymax - UI_searchbox_size_y();
 | 
						|
 | 
						|
		ofsx = (but->block->panel) ? but->block->panel->ofsx : 0;
 | 
						|
		ofsy = (but->block->panel) ? but->block->panel->ofsy : 0;
 | 
						|
 | 
						|
		BLI_rctf_translate(&rect_fl, ofsx, ofsy);
 | 
						|
 | 
						|
		/* minimal width */
 | 
						|
		if (BLI_rctf_size_x(&rect_fl) < searchbox_width) {
 | 
						|
			rect_fl.xmax = rect_fl.xmin + searchbox_width;
 | 
						|
		}
 | 
						|
 | 
						|
		/* copy to int, gets projected if possible too */
 | 
						|
		BLI_rcti_rctf_copy(&rect_i, &rect_fl);
 | 
						|
 | 
						|
		if (butregion->v2d.cur.xmin != butregion->v2d.cur.xmax) {
 | 
						|
			UI_view2d_view_to_region_rcti(&butregion->v2d, &rect_fl, &rect_i);
 | 
						|
		}
 | 
						|
 | 
						|
		BLI_rcti_translate(&rect_i, butregion->winrct.xmin, butregion->winrct.ymin);
 | 
						|
 | 
						|
		winx = WM_window_pixels_x(win);
 | 
						|
		// winy = WM_window_pixels_y(win);  /* UNUSED */
 | 
						|
		//wm_window_get_size(win, &winx, &winy);
 | 
						|
 | 
						|
		if (rect_i.xmax > winx) {
 | 
						|
			/* super size */
 | 
						|
			if (rect_i.xmax > winx + rect_i.xmin) {
 | 
						|
				rect_i.xmax = winx;
 | 
						|
				rect_i.xmin = 0;
 | 
						|
			}
 | 
						|
			else {
 | 
						|
				rect_i.xmin -= rect_i.xmax - winx;
 | 
						|
				rect_i.xmax = winx;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if (rect_i.ymin < 0) {
 | 
						|
			int newy1 = but->rect.ymax + ofsy;
 | 
						|
 | 
						|
			if (butregion->v2d.cur.xmin != butregion->v2d.cur.xmax)
 | 
						|
				newy1 = UI_view2d_view_to_region_y(&butregion->v2d, newy1);
 | 
						|
 | 
						|
			newy1 += butregion->winrct.ymin;
 | 
						|
 | 
						|
			rect_i.ymax = BLI_rcti_size_y(&rect_i) + newy1;
 | 
						|
			rect_i.ymin = newy1;
 | 
						|
		}
 | 
						|
 | 
						|
		/* widget rect, in region coords */
 | 
						|
		data->bbox.xmin = margin;
 | 
						|
		data->bbox.xmax = BLI_rcti_size_x(&rect_i) + margin;
 | 
						|
		data->bbox.ymin = margin;
 | 
						|
		data->bbox.ymax = BLI_rcti_size_y(&rect_i) + margin;
 | 
						|
 | 
						|
		/* region bigger for shadow */
 | 
						|
		ar->winrct.xmin = rect_i.xmin - margin;
 | 
						|
		ar->winrct.xmax = rect_i.xmax + margin;
 | 
						|
		ar->winrct.ymin = rect_i.ymin - margin;
 | 
						|
		ar->winrct.ymax = rect_i.ymax;
 | 
						|
	}
 | 
						|
 | 
						|
	/* adds subwindow */
 | 
						|
	ED_region_init(C, ar);
 | 
						|
 | 
						|
	/* notify change and redraw */
 | 
						|
	ED_region_tag_redraw(ar);
 | 
						|
 | 
						|
	/* prepare search data */
 | 
						|
	if (data->preview) {
 | 
						|
		data->items.maxitem = data->prv_rows * data->prv_cols;
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		data->items.maxitem = SEARCH_ITEMS;
 | 
						|
	}
 | 
						|
	data->items.maxstrlen = but->hardmax;
 | 
						|
	data->items.totitem = 0;
 | 
						|
	data->items.names = MEM_callocN(data->items.maxitem * sizeof(void *), "search names");
 | 
						|
	data->items.pointers = MEM_callocN(data->items.maxitem * sizeof(void *), "search pointers");
 | 
						|
	data->items.icons = MEM_callocN(data->items.maxitem * sizeof(int), "search icons");
 | 
						|
	for (i = 0; i < data->items.maxitem; i++)
 | 
						|
		data->items.names[i] = MEM_callocN(but->hardmax + 1, "search pointers");
 | 
						|
 | 
						|
	return ar;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Similar to Python's `str.title` except...
 | 
						|
 *
 | 
						|
 * - we know words are upper case and ascii only.
 | 
						|
 * - '_' are replaces by spaces.
 | 
						|
 */
 | 
						|
static void str_tolower_titlecaps_ascii(char *str, const size_t len)
 | 
						|
{
 | 
						|
	size_t i;
 | 
						|
	bool prev_delim = true;
 | 
						|
 | 
						|
	for (i = 0; (i < len) && str[i]; i++) {
 | 
						|
		if (str[i] >= 'A' && str[i] <= 'Z') {
 | 
						|
			if (prev_delim == false) {
 | 
						|
				str[i] += 'a' - 'A';
 | 
						|
			}
 | 
						|
		}
 | 
						|
		else if (str[i] == '_') {
 | 
						|
			str[i] = ' ';
 | 
						|
		}
 | 
						|
 | 
						|
		prev_delim = ELEM(str[i], ' ') || (str[i] >= '0' && str[i] <= '9');
 | 
						|
	}
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
static void ui_searchbox_region_draw_cb__operator(const bContext *UNUSED(C), ARegion *ar)
 | 
						|
{
 | 
						|
	uiSearchboxData *data = ar->regiondata;
 | 
						|
 | 
						|
	/* pixel space */
 | 
						|
	wmOrtho2_region_pixelspace(ar);
 | 
						|
 | 
						|
	if (data->noback == false)
 | 
						|
		ui_draw_search_back(NULL, NULL, &data->bbox);  /* style not used yet */
 | 
						|
 | 
						|
	/* draw text */
 | 
						|
	if (data->items.totitem) {
 | 
						|
		rcti rect;
 | 
						|
		int a;
 | 
						|
 | 
						|
		/* draw items */
 | 
						|
		for (a = 0; a < data->items.totitem; a++) {
 | 
						|
			rcti rect_pre, rect_post;
 | 
						|
			ui_searchbox_butrect(&rect, data, a);
 | 
						|
 | 
						|
			rect_pre  = rect;
 | 
						|
			rect_post = rect;
 | 
						|
 | 
						|
			rect_pre.xmax = rect_post.xmin = rect.xmin + ((rect.xmax - rect.xmin) / 4);
 | 
						|
 | 
						|
			/* widget itself */
 | 
						|
			/* NOTE: i18n messages extracting tool does the same, please keep it in sync. */
 | 
						|
			{
 | 
						|
				wmOperatorType *ot = data->items.pointers[a];
 | 
						|
 | 
						|
				int state = (a == data->active) ? UI_ACTIVE : 0;
 | 
						|
				char  text_pre[128];
 | 
						|
				char *text_pre_p = strstr(ot->idname, "_OT_");
 | 
						|
				if (text_pre_p == NULL) {
 | 
						|
					text_pre[0] = '\0';
 | 
						|
				}
 | 
						|
				else {
 | 
						|
					int text_pre_len;
 | 
						|
					text_pre_p += 1;
 | 
						|
					text_pre_len = BLI_strncpy_rlen(
 | 
						|
					        text_pre, ot->idname, min_ii(sizeof(text_pre), text_pre_p - ot->idname));
 | 
						|
					text_pre[text_pre_len] = ':';
 | 
						|
					text_pre[text_pre_len + 1] = '\0';
 | 
						|
					str_tolower_titlecaps_ascii(text_pre, sizeof(text_pre));
 | 
						|
				}
 | 
						|
 | 
						|
				rect_pre.xmax += 4;  /* sneaky, avoid showing ugly margin */
 | 
						|
				ui_draw_menu_item(&data->fstyle, &rect_pre, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, text_pre),
 | 
						|
				                  data->items.icons[a], state, false);
 | 
						|
				ui_draw_menu_item(&data->fstyle, &rect_post, data->items.names[a], 0, state, data->use_sep);
 | 
						|
			}
 | 
						|
 | 
						|
		}
 | 
						|
		/* indicate more */
 | 
						|
		if (data->items.more) {
 | 
						|
			ui_searchbox_butrect(&rect, data, data->items.maxitem - 1);
 | 
						|
			glEnable(GL_BLEND);
 | 
						|
			UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymin - 9, ICON_TRIA_DOWN);
 | 
						|
			glDisable(GL_BLEND);
 | 
						|
		}
 | 
						|
		if (data->items.offset) {
 | 
						|
			ui_searchbox_butrect(&rect, data, 0);
 | 
						|
			glEnable(GL_BLEND);
 | 
						|
			UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymax - 7, ICON_TRIA_UP);
 | 
						|
			glDisable(GL_BLEND);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
ARegion *ui_searchbox_create_operator(bContext *C, ARegion *butregion, uiBut *but)
 | 
						|
{
 | 
						|
	ARegion *ar;
 | 
						|
 | 
						|
	ar = ui_searchbox_create_generic(C, butregion, but);
 | 
						|
 | 
						|
	ar->type->draw = ui_searchbox_region_draw_cb__operator;
 | 
						|
 | 
						|
	return ar;
 | 
						|
}
 | 
						|
 | 
						|
void ui_searchbox_free(bContext *C, ARegion *ar)
 | 
						|
{
 | 
						|
	ui_region_temp_remove(C, CTX_wm_screen(C), ar);
 | 
						|
}
 | 
						|
 | 
						|
/* sets red alert if button holds a string it can't find */
 | 
						|
/* XXX weak: search_func adds all partial matches... */
 | 
						|
void ui_but_search_refresh(uiBut *but)
 | 
						|
{
 | 
						|
	uiSearchItems *items;
 | 
						|
	int x1;
 | 
						|
 | 
						|
	/* possibly very large lists (such as ID datablocks) only
 | 
						|
	 * only validate string RNA buts (not pointers) */
 | 
						|
	if (but->rnaprop && RNA_property_type(but->rnaprop) != PROP_STRING) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	items = MEM_callocN(sizeof(uiSearchItems), "search items");
 | 
						|
 | 
						|
	/* setup search struct */
 | 
						|
	items->maxitem = 10;
 | 
						|
	items->maxstrlen = 256;
 | 
						|
	items->names = MEM_callocN(items->maxitem * sizeof(void *), "search names");
 | 
						|
	for (x1 = 0; x1 < items->maxitem; x1++)
 | 
						|
		items->names[x1] = MEM_callocN(but->hardmax + 1, "search names");
 | 
						|
 | 
						|
	but->search_func(but->block->evil_C, but->search_arg, but->drawstr, items);
 | 
						|
 | 
						|
	/* only redalert when we are sure of it, this can miss cases when >10 matches */
 | 
						|
	if (items->totitem == 0) {
 | 
						|
		UI_but_flag_enable(but, UI_BUT_REDALERT);
 | 
						|
	}
 | 
						|
	else if (items->more == 0) {
 | 
						|
		if (UI_search_items_find_index(items, but->drawstr) == -1) {
 | 
						|
			UI_but_flag_enable(but, UI_BUT_REDALERT);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for (x1 = 0; x1 < items->maxitem; x1++) {
 | 
						|
		MEM_freeN(items->names[x1]);
 | 
						|
	}
 | 
						|
	MEM_freeN(items->names);
 | 
						|
	MEM_freeN(items);
 | 
						|
}
 | 
						|
 | 
						|
/** \} */
 |