This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/editors/space_node/node_select.c
Campbell Barton 82e8e5c871 Cleanup: move click/drag events to functions
Simplifies future changes to dragging checks and avoids
each check for drag using slightly different logic.
2019-05-30 15:02:53 +10:00

1288 lines
34 KiB
C

/*
* 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.
*/
/** \file
* \ingroup spnode
*/
#include <stdlib.h>
#include "DNA_node_types.h"
#include "BLI_utildefines.h"
#include "BLI_rect.h"
#include "BLI_lasso_2d.h"
#include "BLI_math.h"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BKE_context.h"
#include "BKE_main.h"
#include "BKE_node.h"
#include "ED_node.h" /* own include */
#include "ED_screen.h"
#include "ED_select_utils.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "WM_api.h"
#include "WM_types.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "UI_view2d.h"
#include "MEM_guardedalloc.h"
#include "node_intern.h" /* own include */
/* -------------------------------------------------------------------- */
/** \name Public Node Selection API
* \{ */
static bNode *node_under_mouse_select(bNodeTree *ntree, int mx, int my)
{
bNode *node;
for (node = ntree->nodes.last; node; node = node->prev) {
if (node->typeinfo->select_area_func) {
if (node->typeinfo->select_area_func(node, mx, my)) {
return node;
}
}
}
return NULL;
}
static bNode *node_under_mouse_tweak(bNodeTree *ntree, int mx, int my)
{
bNode *node;
for (node = ntree->nodes.last; node; node = node->prev) {
if (node->typeinfo->tweak_area_func) {
if (node->typeinfo->tweak_area_func(node, mx, my)) {
return node;
}
}
}
return NULL;
}
static bool is_position_over_node_or_socket(SpaceNode *snode, float mouse[2])
{
if (node_under_mouse_tweak(snode->edittree, mouse[0], mouse[1])) {
return true;
}
bNode *node;
bNodeSocket *sock;
if (node_find_indicated_socket(snode, &node, &sock, mouse, SOCK_IN | SOCK_OUT)) {
return true;
}
return false;
}
static bool is_event_over_node_or_socket(bContext *C, const wmEvent *event)
{
SpaceNode *snode = CTX_wm_space_node(C);
ARegion *ar = CTX_wm_region(C);
float mouse[2];
UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &mouse[0], &mouse[1]);
return is_position_over_node_or_socket(snode, mouse);
}
static void node_toggle(bNode *node)
{
nodeSetSelected(node, !(node->flag & SELECT));
}
void node_socket_select(bNode *node, bNodeSocket *sock)
{
sock->flag |= SELECT;
/* select node too */
if (node) {
node->flag |= SELECT;
}
}
void node_socket_deselect(bNode *node, bNodeSocket *sock, const bool deselect_node)
{
sock->flag &= ~SELECT;
if (node && deselect_node) {
bool sel = 0;
/* if no selected sockets remain, also deselect the node */
for (sock = node->inputs.first; sock; sock = sock->next) {
if (sock->flag & SELECT) {
sel = 1;
break;
}
}
for (sock = node->outputs.first; sock; sock = sock->next) {
if (sock->flag & SELECT) {
sel = 1;
break;
}
}
if (!sel) {
node->flag &= ~SELECT;
}
}
}
static void node_socket_toggle(bNode *node, bNodeSocket *sock, int deselect_node)
{
if (sock->flag & SELECT) {
node_socket_deselect(node, sock, deselect_node);
}
else {
node_socket_select(node, sock);
}
}
/* no undo here! */
void node_deselect_all(SpaceNode *snode)
{
bNode *node;
for (node = snode->edittree->nodes.first; node; node = node->next) {
nodeSetSelected(node, false);
}
}
void node_deselect_all_input_sockets(SpaceNode *snode, const bool deselect_nodes)
{
bNode *node;
bNodeSocket *sock;
/* XXX not calling node_socket_deselect here each time, because this does iteration
* over all node sockets internally to check if the node stays selected.
* We can do that more efficiently here.
*/
for (node = snode->edittree->nodes.first; node; node = node->next) {
int sel = 0;
for (sock = node->inputs.first; sock; sock = sock->next) {
sock->flag &= ~SELECT;
}
/* if no selected sockets remain, also deselect the node */
if (deselect_nodes) {
for (sock = node->outputs.first; sock; sock = sock->next) {
if (sock->flag & SELECT) {
sel = 1;
break;
}
}
if (!sel) {
node->flag &= ~SELECT;
}
}
}
}
void node_deselect_all_output_sockets(SpaceNode *snode, const bool deselect_nodes)
{
bNode *node;
bNodeSocket *sock;
/* XXX not calling node_socket_deselect here each time, because this does iteration
* over all node sockets internally to check if the node stays selected.
* We can do that more efficiently here.
*/
for (node = snode->edittree->nodes.first; node; node = node->next) {
bool sel = false;
for (sock = node->outputs.first; sock; sock = sock->next) {
sock->flag &= ~SELECT;
}
/* if no selected sockets remain, also deselect the node */
if (deselect_nodes) {
for (sock = node->inputs.first; sock; sock = sock->next) {
if (sock->flag & SELECT) {
sel = 1;
break;
}
}
if (!sel) {
node->flag &= ~SELECT;
}
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Grouped Operator
* \{ */
/* Return true if we need redraw, otherwise false. */
static bool node_select_grouped_type(SpaceNode *snode, bNode *node_act)
{
bNode *node;
bool changed = false;
for (node = snode->edittree->nodes.first; node; node = node->next) {
if ((node->flag & SELECT) == 0) {
if (node->type == node_act->type) {
nodeSetSelected(node, true);
changed = true;
}
}
}
return changed;
}
static bool node_select_grouped_color(SpaceNode *snode, bNode *node_act)
{
bNode *node;
bool changed = false;
for (node = snode->edittree->nodes.first; node; node = node->next) {
if ((node->flag & SELECT) == 0) {
if (compare_v3v3(node->color, node_act->color, 0.005f)) {
nodeSetSelected(node, true);
changed = true;
}
}
}
return changed;
}
static bool node_select_grouped_name(SpaceNode *snode, bNode *node_act, const bool from_right)
{
bNode *node;
bool changed = false;
const unsigned int delims[] = {'.', '-', '_', '\0'};
size_t pref_len_act, pref_len_curr;
const char *sep, *suf_act, *suf_curr;
pref_len_act = BLI_str_partition_ex_utf8(
node_act->name, NULL, delims, &sep, &suf_act, from_right);
/* Note: in case we are searching for suffix, and found none, use whole name as suffix. */
if (from_right && !(sep && suf_act)) {
pref_len_act = 0;
suf_act = node_act->name;
}
for (node = snode->edittree->nodes.first; node; node = node->next) {
if (node->flag & SELECT) {
continue;
}
pref_len_curr = BLI_str_partition_ex_utf8(
node->name, NULL, delims, &sep, &suf_curr, from_right);
/* Same as with active node name! */
if (from_right && !(sep && suf_curr)) {
pref_len_curr = 0;
suf_curr = node->name;
}
if ((from_right && STREQ(suf_act, suf_curr)) ||
(!from_right && (pref_len_act == pref_len_curr) &&
STREQLEN(node_act->name, node->name, pref_len_act))) {
nodeSetSelected(node, true);
changed = true;
}
}
return changed;
}
enum {
NODE_SELECT_GROUPED_TYPE = 0,
NODE_SELECT_GROUPED_COLOR = 1,
NODE_SELECT_GROUPED_PREFIX = 2,
NODE_SELECT_GROUPED_SUFIX = 3,
};
static int node_select_grouped_exec(bContext *C, wmOperator *op)
{
SpaceNode *snode = CTX_wm_space_node(C);
bNode *node_act = nodeGetActive(snode->edittree);
bNode *node;
bool changed = false;
const bool extend = RNA_boolean_get(op->ptr, "extend");
const int type = RNA_enum_get(op->ptr, "type");
if (!extend) {
for (node = snode->edittree->nodes.first; node; node = node->next) {
nodeSetSelected(node, false);
}
}
nodeSetSelected(node_act, true);
switch (type) {
case NODE_SELECT_GROUPED_TYPE:
changed = node_select_grouped_type(snode, node_act);
break;
case NODE_SELECT_GROUPED_COLOR:
changed = node_select_grouped_color(snode, node_act);
break;
case NODE_SELECT_GROUPED_PREFIX:
changed = node_select_grouped_name(snode, node_act, false);
break;
case NODE_SELECT_GROUPED_SUFIX:
changed = node_select_grouped_name(snode, node_act, true);
break;
default:
break;
}
if (changed) {
ED_node_sort(snode->edittree);
WM_event_add_notifier(C, NC_NODE | NA_SELECTED, NULL);
return OPERATOR_FINISHED;
}
return OPERATOR_CANCELLED;
}
void NODE_OT_select_grouped(wmOperatorType *ot)
{
static const EnumPropertyItem prop_select_grouped_types[] = {
{NODE_SELECT_GROUPED_TYPE, "TYPE", 0, "Type", ""},
{NODE_SELECT_GROUPED_COLOR, "COLOR", 0, "Color", ""},
{NODE_SELECT_GROUPED_PREFIX, "PREFIX", 0, "Prefix", ""},
{NODE_SELECT_GROUPED_SUFIX, "SUFFIX", 0, "Suffix", ""},
{0, NULL, 0, NULL, NULL},
};
/* identifiers */
ot->name = "Select Grouped";
ot->description = "Select nodes with similar properties";
ot->idname = "NODE_OT_select_grouped";
/* api callbacks */
ot->invoke = WM_menu_invoke;
ot->exec = node_select_grouped_exec;
ot->poll = ED_operator_node_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
RNA_def_boolean(ot->srna,
"extend",
false,
"Extend",
"Extend selection instead of deselecting everything first");
ot->prop = RNA_def_enum(ot->srna, "type", prop_select_grouped_types, 0, "Type", "");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select (Cursor Pick) Operator
* \{ */
void node_select_single(bContext *C, bNode *node)
{
Main *bmain = CTX_data_main(C);
SpaceNode *snode = CTX_wm_space_node(C);
bNode *tnode;
for (tnode = snode->edittree->nodes.first; tnode; tnode = tnode->next) {
if (tnode != node) {
nodeSetSelected(tnode, false);
}
}
nodeSetSelected(node, true);
ED_node_set_active(bmain, snode->edittree, node);
ED_node_set_active_viewer_key(snode);
ED_node_sort(snode->edittree);
WM_event_add_notifier(C, NC_NODE | NA_SELECTED, NULL);
}
static int node_mouse_select(bContext *C,
const int mval[2],
const bool extend,
const bool socket_select,
const bool deselect_all,
const bool wait_to_deselect_others)
{
Main *bmain = CTX_data_main(C);
SpaceNode *snode = CTX_wm_space_node(C);
ARegion *ar = CTX_wm_region(C);
bNode *node, *tnode;
bNodeSocket *sock = NULL;
bNodeSocket *tsock;
float cursor[2];
int ret_value = OPERATOR_CANCELLED;
/* Waiting to deselect others is only allowed for basic selection. */
BLI_assert(!(extend || socket_select) || !wait_to_deselect_others);
/* get mouse coordinates in view2d space */
UI_view2d_region_to_view(&ar->v2d, mval[0], mval[1], &cursor[0], &cursor[1]);
/* first do socket selection, these generally overlap with nodes. */
if (socket_select) {
if (node_find_indicated_socket(snode, &node, &sock, cursor, SOCK_IN)) {
/* NOTE: SOCK_IN does not take into account the extend case...
* This feature is not really used anyway currently? */
node_socket_toggle(node, sock, true);
ret_value = OPERATOR_FINISHED;
}
else if (node_find_indicated_socket(snode, &node, &sock, cursor, SOCK_OUT)) {
if (sock->flag & SELECT) {
if (extend) {
node_socket_deselect(node, sock, true);
}
else {
ret_value = OPERATOR_FINISHED;
}
}
else {
/* Only allow one selected output per node, for sensible linking.
* Allow selecting outputs from different nodes though, if extend is true. */
if (node) {
for (tsock = node->outputs.first; tsock; tsock = tsock->next) {
if (tsock == sock) {
continue;
}
node_socket_deselect(node, tsock, true);
}
}
if (!extend) {
for (tnode = snode->edittree->nodes.first; tnode; tnode = tnode->next) {
if (tnode == node) {
continue;
}
for (tsock = tnode->outputs.first; tsock; tsock = tsock->next) {
node_socket_deselect(tnode, tsock, true);
}
}
}
node_socket_select(node, sock);
ret_value = OPERATOR_FINISHED;
}
}
}
if (!sock) {
/* find the closest visible node */
node = node_under_mouse_select(snode->edittree, (int)cursor[0], (int)cursor[1]);
if (extend) {
if (node != NULL) {
/* If node is selected but not active, we want to make it active,
* but not toggle (deselect) it. */
if (!((node->flag & SELECT) && (node->flag & NODE_ACTIVE) == 0)) {
node_toggle(node);
}
ret_value = OPERATOR_FINISHED;
}
}
else if (deselect_all && node == NULL) {
/* Deselect in empty space. */
for (tnode = snode->edittree->nodes.first; tnode; tnode = tnode->next) {
nodeSetSelected(tnode, false);
}
ret_value = OPERATOR_FINISHED;
}
else if (node != NULL) {
/* When clicking on an already selected node, we want to wait to deselect
* others and allow the user to start moving the node without that. */
if (wait_to_deselect_others && (node->flag & SELECT)) {
ret_value = OPERATOR_RUNNING_MODAL;
}
else {
nodeSetSelected(node, true);
for (tnode = snode->edittree->nodes.first; tnode; tnode = tnode->next) {
if (tnode != node) {
nodeSetSelected(tnode, false);
}
}
ret_value = OPERATOR_FINISHED;
}
}
}
/* update node order */
if (ret_value != OPERATOR_CANCELLED) {
if (node != NULL && ret_value != OPERATOR_RUNNING_MODAL) {
ED_node_set_active(bmain, snode->edittree, node);
}
ED_node_set_active_viewer_key(snode);
ED_node_sort(snode->edittree);
WM_event_add_notifier(C, NC_NODE | NA_SELECTED, NULL);
}
return ret_value;
}
static int node_select_exec(bContext *C, wmOperator *op)
{
int mval[2];
/* get settings from RNA properties for operator */
mval[0] = RNA_int_get(op->ptr, "mouse_x");
mval[1] = RNA_int_get(op->ptr, "mouse_y");
const bool extend = RNA_boolean_get(op->ptr, "extend");
/* always do socket_select when extending selection. */
const bool socket_select = extend || RNA_boolean_get(op->ptr, "socket_select");
const bool deselect_all = RNA_boolean_get(op->ptr, "deselect_all");
/* perform the select */
const int ret_value = node_mouse_select(C, mval, extend, socket_select, deselect_all, false);
/* allow tweak event to work too */
return ret_value | OPERATOR_PASS_THROUGH;
}
static int node_select_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
const short init_event_type = (short)POINTER_AS_INT(op->customdata);
/* get settings from RNA properties for operator */
int mval[2];
mval[0] = RNA_int_get(op->ptr, "mouse_x");
mval[1] = RNA_int_get(op->ptr, "mouse_y");
const bool extend = RNA_boolean_get(op->ptr, "extend");
/* always do socket_select when extending selection. */
const bool socket_select = extend || RNA_boolean_get(op->ptr, "socket_select");
const bool deselect_all = RNA_boolean_get(op->ptr, "deselect_all");
/* These cases are never modal. */
if (extend || socket_select) {
return node_select_exec(C, op);
}
if (init_event_type == 0) {
if (event->val == KM_PRESS) {
const int ret_value = node_mouse_select(C, mval, extend, socket_select, deselect_all, true);
op->customdata = POINTER_FROM_INT((int)event->type);
if (ret_value & OPERATOR_RUNNING_MODAL) {
WM_event_add_modal_handler(C, op);
}
return ret_value | OPERATOR_PASS_THROUGH;
}
else {
/* If we are in init phase, and cannot validate init of modal operations,
* just fall back to basic exec.
*/
return node_select_exec(C, op);
}
}
else if (event->type == init_event_type && event->val == KM_RELEASE) {
const int ret_value = node_mouse_select(C, mval, extend, socket_select, deselect_all, false);
return ret_value | OPERATOR_PASS_THROUGH;
}
else if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) {
const int drag_delta[2] = {
mval[0] - event->mval[0],
mval[1] - event->mval[1],
};
/* If user moves mouse more than defined threshold, we consider select operator as
* finished. Otherwise, it is still running until we get an 'release' event. In any
* case, we pass through event, but select op is not finished yet. */
if (WM_event_drag_test_with_delta(event, drag_delta)) {
return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
}
else {
/* Important not to return anything other than PASS_THROUGH here,
* otherwise it prevents underlying tweak detection code to work properly. */
return OPERATOR_PASS_THROUGH;
}
}
return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
}
static int node_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
RNA_int_set(op->ptr, "mouse_x", event->mval[0]);
RNA_int_set(op->ptr, "mouse_y", event->mval[1]);
op->customdata = POINTER_FROM_INT(0);
return node_select_modal(C, op, event);
}
void NODE_OT_select(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select";
ot->idname = "NODE_OT_select";
ot->description = "Select the node under the cursor";
/* api callbacks */
ot->invoke = node_select_invoke;
ot->exec = node_select_exec;
ot->modal = node_select_modal;
ot->poll = ED_operator_node_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
PropertyRNA *prop;
RNA_def_int(ot->srna, "mouse_x", 0, INT_MIN, INT_MAX, "Mouse X", "", INT_MIN, INT_MAX);
RNA_def_int(ot->srna, "mouse_y", 0, INT_MIN, INT_MAX, "Mouse Y", "", INT_MIN, INT_MAX);
RNA_def_boolean(ot->srna, "extend", false, "Extend", "");
RNA_def_boolean(ot->srna, "socket_select", false, "Socket Select", "");
prop = RNA_def_boolean(ot->srna,
"deselect_all",
false,
"Deselect On Nothing",
"Deselect all when nothing under the cursor");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Box Select Operator
* \{ */
static int node_box_select_exec(bContext *C, wmOperator *op)
{
SpaceNode *snode = CTX_wm_space_node(C);
ARegion *ar = CTX_wm_region(C);
rctf rectf;
WM_operator_properties_border_to_rctf(op, &rectf);
UI_view2d_region_to_view_rctf(&ar->v2d, &rectf, &rectf);
const eSelectOp sel_op = RNA_enum_get(op->ptr, "mode");
const bool select = (sel_op != SEL_OP_SUB);
if (SEL_OP_USE_PRE_DESELECT(sel_op)) {
ED_node_select_all(&snode->edittree->nodes, SEL_DESELECT);
}
for (bNode *node = snode->edittree->nodes.first; node; node = node->next) {
bool is_inside;
if (node->type == NODE_FRAME) {
is_inside = BLI_rctf_inside_rctf(&rectf, &node->totr);
}
else {
is_inside = BLI_rctf_isect(&rectf, &node->totr, NULL);
}
if (is_inside) {
nodeSetSelected(node, select);
}
}
ED_node_sort(snode->edittree);
WM_event_add_notifier(C, NC_NODE | NA_SELECTED, NULL);
return OPERATOR_FINISHED;
}
static int node_box_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
const bool tweak = RNA_boolean_get(op->ptr, "tweak");
if (tweak && is_event_over_node_or_socket(C, event)) {
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
}
return WM_gesture_box_invoke(C, op, event);
}
void NODE_OT_select_box(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Box Select";
ot->idname = "NODE_OT_select_box";
ot->description = "Use box selection to select nodes";
/* api callbacks */
ot->invoke = node_box_select_invoke;
ot->exec = node_box_select_exec;
ot->modal = WM_gesture_box_modal;
ot->cancel = WM_gesture_box_cancel;
ot->poll = ED_operator_node_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
RNA_def_boolean(ot->srna,
"tweak",
0,
"Tweak",
"Only activate when mouse is not over a node - useful for tweak gesture");
WM_operator_properties_gesture_box(ot);
WM_operator_properties_select_operation_simple(ot);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Circle Select Operator
* \{ */
static int node_circleselect_exec(bContext *C, wmOperator *op)
{
SpaceNode *snode = CTX_wm_space_node(C);
ARegion *ar = CTX_wm_region(C);
bNode *node;
int x, y, radius;
float offset[2];
float zoom = (float)(BLI_rcti_size_x(&ar->winrct)) / (float)(BLI_rctf_size_x(&ar->v2d.cur));
const eSelectOp sel_op = ED_select_op_modal(RNA_enum_get(op->ptr, "mode"),
WM_gesture_is_modal_first(op->customdata));
const bool select = (sel_op != SEL_OP_SUB);
if (SEL_OP_USE_PRE_DESELECT(sel_op)) {
ED_node_select_all(&snode->edittree->nodes, SEL_DESELECT);
}
/* get operator properties */
x = RNA_int_get(op->ptr, "x");
y = RNA_int_get(op->ptr, "y");
radius = RNA_int_get(op->ptr, "radius");
UI_view2d_region_to_view(&ar->v2d, x, y, &offset[0], &offset[1]);
for (node = snode->edittree->nodes.first; node; node = node->next) {
if (BLI_rctf_isect_circle(&node->totr, offset, radius / zoom)) {
nodeSetSelected(node, select);
}
}
WM_event_add_notifier(C, NC_NODE | NA_SELECTED, NULL);
return OPERATOR_FINISHED;
}
void NODE_OT_select_circle(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Circle Select";
ot->idname = "NODE_OT_select_circle";
ot->description = "Use circle selection to select nodes";
/* api callbacks */
ot->invoke = WM_gesture_circle_invoke;
ot->exec = node_circleselect_exec;
ot->modal = WM_gesture_circle_modal;
ot->poll = ED_operator_node_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
WM_operator_properties_gesture_circle(ot);
WM_operator_properties_select_operation_simple(ot);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Lasso Select Operator
* \{ */
static int node_lasso_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
const bool tweak = RNA_boolean_get(op->ptr, "tweak");
if (tweak && is_event_over_node_or_socket(C, event)) {
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
}
return WM_gesture_lasso_invoke(C, op, event);
}
static bool do_lasso_select_node(bContext *C, const int mcords[][2], short moves, eSelectOp sel_op)
{
SpaceNode *snode = CTX_wm_space_node(C);
bNode *node;
ARegion *ar = CTX_wm_region(C);
rcti rect;
bool changed = false;
const bool select = (sel_op != SEL_OP_SUB);
if (SEL_OP_USE_PRE_DESELECT(sel_op)) {
ED_node_select_all(&snode->edittree->nodes, SEL_DESELECT);
changed = true;
}
/* get rectangle from operator */
BLI_lasso_boundbox(&rect, mcords, moves);
/* do actual selection */
for (node = snode->edittree->nodes.first; node; node = node->next) {
if (select && (node->flag & NODE_SELECT)) {
continue;
}
int screen_co[2];
const float cent[2] = {BLI_rctf_cent_x(&node->totr), BLI_rctf_cent_y(&node->totr)};
/* marker in screen coords */
if (UI_view2d_view_to_region_clip(&ar->v2d, cent[0], cent[1], &screen_co[0], &screen_co[1]) &&
BLI_rcti_isect_pt(&rect, screen_co[0], screen_co[1]) &&
BLI_lasso_is_point_inside(mcords, moves, screen_co[0], screen_co[1], INT_MAX)) {
nodeSetSelected(node, select);
changed = true;
}
}
if (changed) {
WM_event_add_notifier(C, NC_NODE | NA_SELECTED, NULL);
}
return changed;
}
static int node_lasso_select_exec(bContext *C, wmOperator *op)
{
int mcords_tot;
const int(*mcords)[2] = WM_gesture_lasso_path_to_array(C, op, &mcords_tot);
if (mcords) {
const eSelectOp sel_op = RNA_enum_get(op->ptr, "mode");
do_lasso_select_node(C, mcords, mcords_tot, sel_op);
MEM_freeN((void *)mcords);
return OPERATOR_FINISHED;
}
return OPERATOR_PASS_THROUGH;
}
void NODE_OT_select_lasso(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Lasso Select";
ot->description = "Select nodes using lasso selection";
ot->idname = "NODE_OT_select_lasso";
/* api callbacks */
ot->invoke = node_lasso_select_invoke;
ot->modal = WM_gesture_lasso_modal;
ot->exec = node_lasso_select_exec;
ot->poll = ED_operator_node_active;
ot->cancel = WM_gesture_lasso_cancel;
/* flags */
ot->flag = OPTYPE_UNDO;
/* properties */
RNA_def_boolean(ot->srna,
"tweak",
0,
"Tweak",
"Only activate when mouse is not over a node - useful for tweak gesture");
WM_operator_properties_gesture_lasso(ot);
WM_operator_properties_select_operation_simple(ot);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name (De)select All Operator
* \{ */
static int node_select_all_exec(bContext *C, wmOperator *op)
{
SpaceNode *snode = CTX_wm_space_node(C);
ListBase *node_lb = &snode->edittree->nodes;
int action = RNA_enum_get(op->ptr, "action");
ED_node_select_all(node_lb, action);
ED_node_sort(snode->edittree);
WM_event_add_notifier(C, NC_NODE | NA_SELECTED, NULL);
return OPERATOR_FINISHED;
}
void NODE_OT_select_all(wmOperatorType *ot)
{
/* identifiers */
ot->name = "(De)select All";
ot->description = "(De)select all nodes";
ot->idname = "NODE_OT_select_all";
/* api callbacks */
ot->exec = node_select_all_exec;
ot->poll = ED_operator_node_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
WM_operator_properties_select_all(ot);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Linked To Operator
* \{ */
static int node_select_linked_to_exec(bContext *C, wmOperator *UNUSED(op))
{
SpaceNode *snode = CTX_wm_space_node(C);
bNodeLink *link;
bNode *node;
for (node = snode->edittree->nodes.first; node; node = node->next) {
node->flag &= ~NODE_TEST;
}
for (link = snode->edittree->links.first; link; link = link->next) {
if (nodeLinkIsHidden(link)) {
continue;
}
if (link->fromnode && link->tonode && (link->fromnode->flag & NODE_SELECT)) {
link->tonode->flag |= NODE_TEST;
}
}
for (node = snode->edittree->nodes.first; node; node = node->next) {
if (node->flag & NODE_TEST) {
nodeSetSelected(node, true);
}
}
ED_node_sort(snode->edittree);
WM_event_add_notifier(C, NC_NODE | NA_SELECTED, NULL);
return OPERATOR_FINISHED;
}
void NODE_OT_select_linked_to(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Linked To";
ot->description = "Select nodes linked to the selected ones";
ot->idname = "NODE_OT_select_linked_to";
/* api callbacks */
ot->exec = node_select_linked_to_exec;
ot->poll = ED_operator_node_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Linked From Operator
* \{ */
static int node_select_linked_from_exec(bContext *C, wmOperator *UNUSED(op))
{
SpaceNode *snode = CTX_wm_space_node(C);
bNodeLink *link;
bNode *node;
for (node = snode->edittree->nodes.first; node; node = node->next) {
node->flag &= ~NODE_TEST;
}
for (link = snode->edittree->links.first; link; link = link->next) {
if (nodeLinkIsHidden(link)) {
continue;
}
if (link->fromnode && link->tonode && (link->tonode->flag & NODE_SELECT)) {
link->fromnode->flag |= NODE_TEST;
}
}
for (node = snode->edittree->nodes.first; node; node = node->next) {
if (node->flag & NODE_TEST) {
nodeSetSelected(node, true);
}
}
ED_node_sort(snode->edittree);
WM_event_add_notifier(C, NC_NODE | NA_SELECTED, NULL);
return OPERATOR_FINISHED;
}
void NODE_OT_select_linked_from(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Linked From";
ot->description = "Select nodes linked from the selected ones";
ot->idname = "NODE_OT_select_linked_from";
/* api callbacks */
ot->exec = node_select_linked_from_exec;
ot->poll = ED_operator_node_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Same Type Step Operator
* \{ */
static int node_select_same_type_step_exec(bContext *C, wmOperator *op)
{
SpaceNode *snode = CTX_wm_space_node(C);
ARegion *ar = CTX_wm_region(C);
bNode **node_array;
bNode *active = nodeGetActive(snode->edittree);
int totnodes;
const bool revert = RNA_boolean_get(op->ptr, "prev");
const bool same_type = 1;
ntreeGetDependencyList(snode->edittree, &node_array, &totnodes);
if (totnodes > 1) {
int a;
for (a = 0; a < totnodes; a++) {
if (node_array[a] == active) {
break;
}
}
if (same_type) {
bNode *node = NULL;
while (node == NULL) {
if (revert) {
a--;
}
else {
a++;
}
if (a < 0 || a >= totnodes) {
break;
}
node = node_array[a];
if (node->type == active->type) {
break;
}
else {
node = NULL;
}
}
if (node) {
active = node;
}
}
else {
if (revert) {
if (a == 0) {
active = node_array[totnodes - 1];
}
else {
active = node_array[a - 1];
}
}
else {
if (a == totnodes - 1) {
active = node_array[0];
}
else {
active = node_array[a + 1];
}
}
}
node_select_single(C, active);
/* is note outside view? */
if (active->totr.xmax < ar->v2d.cur.xmin || active->totr.xmin > ar->v2d.cur.xmax ||
active->totr.ymax < ar->v2d.cur.ymin || active->totr.ymin > ar->v2d.cur.ymax) {
const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
space_node_view_flag(C, snode, ar, NODE_SELECT, smooth_viewtx);
}
}
if (node_array) {
MEM_freeN(node_array);
}
return OPERATOR_FINISHED;
}
void NODE_OT_select_same_type_step(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Activate Same Type Next/Prev";
ot->description = "Activate and view same node type, step by step";
ot->idname = "NODE_OT_select_same_type_step";
/* api callbacks */
ot->exec = node_select_same_type_step_exec;
ot->poll = ED_operator_node_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(ot->srna, "prev", 0, "Previous", "");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Find Node by Name Operator
* \{ */
/* generic search invoke */
static void node_find_cb(const struct bContext *C,
void *UNUSED(arg),
const char *str,
uiSearchItems *items)
{
SpaceNode *snode = CTX_wm_space_node(C);
bNode *node;
for (node = snode->edittree->nodes.first; node; node = node->next) {
if (BLI_strcasestr(node->name, str) || BLI_strcasestr(node->label, str)) {
char name[256];
if (node->label[0]) {
BLI_snprintf(name, 256, "%s (%s)", node->name, node->label);
}
else {
BLI_strncpy(name, node->name, 256);
}
if (false == UI_search_item_add(items, name, node, 0)) {
break;
}
}
}
}
static void node_find_call_cb(struct bContext *C, void *UNUSED(arg1), void *arg2)
{
SpaceNode *snode = CTX_wm_space_node(C);
bNode *active = arg2;
if (active) {
ARegion *ar = CTX_wm_region(C);
node_select_single(C, active);
/* is note outside view? */
if (active->totr.xmax < ar->v2d.cur.xmin || active->totr.xmin > ar->v2d.cur.xmax ||
active->totr.ymax < ar->v2d.cur.ymin || active->totr.ymin > ar->v2d.cur.ymax) {
space_node_view_flag(C, snode, ar, NODE_SELECT, U.smooth_viewtx);
}
}
}
static uiBlock *node_find_menu(bContext *C, ARegion *ar, void *arg_op)
{
static char search[256] = "";
uiBlock *block;
uiBut *but;
wmOperator *op = (wmOperator *)arg_op;
block = UI_block_begin(C, ar, "_popup", UI_EMBOSS);
UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_SEARCH_MENU);
UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
but = uiDefSearchBut(
block, search, 0, ICON_VIEWZOOM, sizeof(search), 10, 10, 9 * UI_UNIT_X, UI_UNIT_Y, 0, 0, "");
UI_but_func_search_set(but, NULL, node_find_cb, op->type, false, node_find_call_cb, NULL);
UI_but_flag_enable(but, UI_BUT_ACTIVATE_ON_INIT);
/* fake button, it holds space for search items */
uiDefBut(block,
UI_BTYPE_LABEL,
0,
"",
10,
10 - UI_searchbox_size_y(),
UI_searchbox_size_x(),
UI_searchbox_size_y(),
NULL,
0,
0,
0,
0,
NULL);
/* Move it downwards, mouse over button. */
UI_block_bounds_set_popup(block, 6, (const int[2]){0, -UI_UNIT_Y});
return block;
}
static int node_find_node_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
UI_popup_block_invoke(C, node_find_menu, op, NULL);
return OPERATOR_CANCELLED;
}
void NODE_OT_find_node(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Find Node";
ot->description = "Search for named node and allow to select and activate it";
ot->idname = "NODE_OT_find_node";
/* api callbacks */
ot->invoke = node_find_node_invoke;
ot->poll = ED_operator_node_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(ot->srna, "prev", 0, "Previous", "");
}
/** \} */