Blender V5.0
spreadsheet_row_filter_ui.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <cstring>
6#include <fmt/format.h>
7#include <sstream>
8
9#include "BLI_listbase.h"
10#include "BLI_string_ref.hh"
11#include "BLI_string_utf8.h"
12
13#include "DNA_screen_types.h"
14#include "DNA_space_types.h"
15
16#include "BKE_screen.hh"
17
18#include "RNA_access.hh"
19#include "RNA_prototypes.hh"
20
21#include "UI_interface.hh"
23#include "UI_resources.hh"
24
25#include "BLT_translation.hh"
26
27#include "WM_api.hh"
28#include "WM_types.hh"
29
30#include "ED_spreadsheet.hh"
31
32#include "spreadsheet_intern.hh"
34
36
37static void filter_panel_id_fn(void * /*row_filter_v*/, char *r_name)
38{
39 /* All row filters use the same panel ID. */
40 BLI_strncpy_utf8(r_name, "SPREADSHEET_PT_filter", BKE_ST_MAXNAME);
41}
42
43static std::string operation_string(const eSpreadsheetColumnValueType data_type,
44 const eSpreadsheetFilterOperation operation)
45{
47 return "=";
48 }
49
50 switch (operation) {
52 return "=";
54 return ">";
56 return "<";
57 }
59 return "";
60}
61
62static std::string value_string(const SpreadsheetRowFilter &row_filter,
63 const eSpreadsheetColumnValueType data_type)
64{
65 switch (data_type) {
69 return std::to_string(row_filter.value_int);
71 std::ostringstream result;
72 result.precision(3);
73 result << std::fixed << row_filter.value_float;
74 return result.str();
75 }
77 std::ostringstream result;
78 result << "(" << row_filter.value_int2[0] << ", " << row_filter.value_int2[1] << ")";
79 return result.str();
80 }
82 std::ostringstream result;
83 return fmt::format("({}, {}, {})",
84 row_filter.value_int3[0],
85 row_filter.value_int3[1],
86 row_filter.value_int3[2]);
87 }
89 std::ostringstream result;
90 result.precision(3);
91 result << std::fixed << "(" << row_filter.value_float2[0] << ", "
92 << row_filter.value_float2[1] << ")";
93 return result.str();
94 }
96 std::ostringstream result;
97 result.precision(3);
98 result << std::fixed << "(" << row_filter.value_float3[0] << ", "
99 << row_filter.value_float3[1] << ", " << row_filter.value_float3[2] << ")";
100 return result.str();
101 }
103 return (row_filter.flag & SPREADSHEET_ROW_FILTER_BOOL_VALUE) ? IFACE_("True") :
104 IFACE_("False");
106 if (row_filter.value_string != nullptr) {
107 return row_filter.value_string;
108 }
109 return "";
112 std::ostringstream result;
113 result.precision(3);
114 result << std::fixed << "(" << row_filter.value_color[0] << ", " << row_filter.value_color[1]
115 << ", " << row_filter.value_color[2] << ", " << row_filter.value_color[3] << ")";
116 return result.str();
117 }
119 return row_filter.value_string;
124 return "";
125 }
127 return "";
128}
129
131 const SpaceSpreadsheet &sspreadsheet, const StringRef column_name)
132{
133 const SpreadsheetTable *table = get_active_table(sspreadsheet);
134 if (!table) {
135 return nullptr;
136 }
137 for (const SpreadsheetColumn *column : Span{table->columns, table->num_columns}) {
138 if (column->display_name == column_name) {
139 return column;
140 }
141 }
142 return nullptr;
143}
144
146{
147 uiLayout *layout = panel->layout;
149 PointerRNA *filter_ptr = UI_panel_custom_data_get(panel);
151 const StringRef column_name = filter->column_name;
153
154 const SpreadsheetColumn *column = lookup_visible_column_for_filter(*sspreadsheet, column_name);
155 if (!(sspreadsheet->filter_flag & SPREADSHEET_FILTER_ENABLE) ||
156 (column == nullptr && !column_name.is_empty()))
157 {
158 layout->active_set(false);
159 }
160
161 uiLayout *row = &layout->row(true);
163 row->prop(filter_ptr, "enabled", UI_ITEM_R_ICON_ONLY, "", ICON_NONE);
164
165 if (column_name.is_empty()) {
166 row->label(IFACE_("Filter"), ICON_NONE);
167 }
168 else if (column == nullptr) {
169 row->label(column_name.data(), ICON_NONE);
170 }
171 else {
173 std::stringstream ss;
174 ss << column_name;
175 ss << " ";
176 ss << operation_string(data_type, operation);
177 ss << " ";
178 ss << value_string(*filter, data_type);
179 row->label(ss.str(), ICON_NONE);
180 }
181
182 row = &layout->row(true);
184 const int current_index = BLI_findindex(&sspreadsheet->row_filters, filter);
185 PointerRNA op_ptr = row->op("SPREADSHEET_OT_remove_row_filter_rule", "", ICON_X);
186 RNA_int_set(&op_ptr, "index", current_index);
187 /* Some padding so the X isn't too close to the drag icon. */
188 layout->separator(0.25f);
189}
190
192{
193 uiLayout *layout = panel->layout;
195 PointerRNA *filter_ptr = UI_panel_custom_data_get(panel);
197 const StringRef column_name = filter->column_name;
199
200 const SpreadsheetColumn *column = lookup_visible_column_for_filter(*sspreadsheet, column_name);
201 if (!(sspreadsheet->filter_flag & SPREADSHEET_FILTER_ENABLE) ||
203 (column == nullptr && !column_name.is_empty()))
204 {
205 layout->active_set(false);
206 }
207
208 layout->use_property_split_set(true);
209 layout->use_property_decorate_set(false);
210
211 layout->prop(filter_ptr, "column_name", UI_ITEM_NONE, IFACE_("Column"), ICON_NONE);
212
213 /* Don't draw settings for filters with no corresponding visible column. */
214 if (column == nullptr || column_name.is_empty()) {
215 return;
216 }
217
218 switch (static_cast<eSpreadsheetColumnValueType>(column->data_type)) {
220 layout->prop(filter_ptr, "operation", UI_ITEM_NONE, std::nullopt, ICON_NONE);
221 layout->prop(filter_ptr, "value_int8", UI_ITEM_NONE, IFACE_("Value"), ICON_NONE);
222 break;
225 layout->prop(filter_ptr, "operation", UI_ITEM_NONE, std::nullopt, ICON_NONE);
226 layout->prop(filter_ptr, "value_int", UI_ITEM_NONE, IFACE_("Value"), ICON_NONE);
227 break;
229 layout->prop(filter_ptr, "operation", UI_ITEM_NONE, std::nullopt, ICON_NONE);
230 layout->prop(filter_ptr, "value_int2", UI_ITEM_NONE, IFACE_("Value"), ICON_NONE);
231 break;
233 layout->prop(filter_ptr, "operation", UI_ITEM_NONE, std::nullopt, ICON_NONE);
234 layout->prop(filter_ptr, "value_int3", UI_ITEM_NONE, IFACE_("Value"), ICON_NONE);
235 break;
237 layout->prop(filter_ptr, "operation", UI_ITEM_NONE, std::nullopt, ICON_NONE);
238 layout->prop(filter_ptr, "value_float", UI_ITEM_NONE, IFACE_("Value"), ICON_NONE);
239 if (operation == SPREADSHEET_ROW_FILTER_EQUAL) {
240 layout->prop(filter_ptr, "threshold", UI_ITEM_NONE, std::nullopt, ICON_NONE);
241 }
242 break;
244 layout->prop(filter_ptr, "operation", UI_ITEM_NONE, std::nullopt, ICON_NONE);
245 layout->prop(filter_ptr, "value_float2", UI_ITEM_NONE, IFACE_("Value"), ICON_NONE);
246 if (operation == SPREADSHEET_ROW_FILTER_EQUAL) {
247 layout->prop(filter_ptr, "threshold", UI_ITEM_NONE, std::nullopt, ICON_NONE);
248 }
249 break;
251 layout->prop(filter_ptr, "operation", UI_ITEM_NONE, std::nullopt, ICON_NONE);
252 layout->prop(filter_ptr, "value_float3", UI_ITEM_NONE, IFACE_("Value"), ICON_NONE);
253 if (operation == SPREADSHEET_ROW_FILTER_EQUAL) {
254 layout->prop(filter_ptr, "threshold", UI_ITEM_NONE, std::nullopt, ICON_NONE);
255 }
256 break;
258 layout->prop(filter_ptr, "value_boolean", UI_ITEM_NONE, IFACE_("Value"), ICON_NONE);
259 break;
261 layout->prop(filter_ptr, "value_string", UI_ITEM_NONE, IFACE_("Value"), ICON_NONE);
262 break;
265 layout->prop(filter_ptr, "operation", UI_ITEM_NONE, std::nullopt, ICON_NONE);
266 layout->prop(filter_ptr, "value_color", UI_ITEM_NONE, IFACE_("Value"), ICON_NONE);
267 if (operation == SPREADSHEET_ROW_FILTER_EQUAL) {
268 layout->prop(filter_ptr, "threshold", UI_ITEM_NONE, std::nullopt, ICON_NONE);
269 }
270 break;
272 layout->prop(filter_ptr, "value_string", UI_ITEM_NONE, IFACE_("Value"), ICON_NONE);
273 break;
278 layout->label(IFACE_("Unsupported column type"), ICON_ERROR);
279 break;
280 }
281}
282
284{
285 uiLayout *layout = panel->layout;
286 ARegion *region = CTX_wm_region(C);
287 bScreen *screen = CTX_wm_screen(C);
289 ListBase *row_filters = &sspreadsheet->row_filters;
290
291 if (!(sspreadsheet->filter_flag & SPREADSHEET_FILTER_ENABLE)) {
292 layout->active_set(false);
293 }
294
295 layout->op("SPREADSHEET_OT_add_row_filter_rule", std::nullopt, ICON_ADD);
296
297 const bool panels_match = UI_panel_list_matches_data(region, row_filters, filter_panel_id_fn);
298
299 if (!panels_match) {
301 LISTBASE_FOREACH (SpreadsheetRowFilter *, row_filter, row_filters) {
302 char panel_idname[MAX_NAME];
303 filter_panel_id_fn(row_filter, panel_idname);
304
305 PointerRNA *filter_ptr = MEM_new<PointerRNA>("panel customdata");
306 *filter_ptr = RNA_pointer_create_discrete(
307 &screen->id, &RNA_SpreadsheetRowFilter, row_filter);
308
309 UI_panel_add_instanced(C, region, &region->panels, panel_idname, filter_ptr);
310 }
311 }
312 else {
313 /* Assuming there's only one group of instanced panels, update the custom data pointers. */
314 Panel *panel_iter = (Panel *)region->panels.first;
315 LISTBASE_FOREACH (SpreadsheetRowFilter *, row_filter, row_filters) {
316
317 /* Move to the next instanced panel corresponding to the next filter. */
318 while ((panel_iter->type == nullptr) || !(panel_iter->type->flag & PANEL_TYPE_INSTANCED)) {
319 panel_iter = panel_iter->next;
320 BLI_assert(panel_iter != nullptr); /* There shouldn't be fewer panels than filters. */
321 }
322
323 PointerRNA *filter_ptr = MEM_new<PointerRNA>("panel customdata");
324 *filter_ptr = RNA_pointer_create_discrete(
325 &screen->id, &RNA_SpreadsheetRowFilter, row_filter);
326 UI_panel_custom_data_set(panel_iter, filter_ptr);
327
328 panel_iter = panel_iter->next;
329 }
330 }
331}
332
333static void filter_reorder(bContext *C, Panel *panel, int new_index)
334{
336 ListBase *row_filters = &sspreadsheet->row_filters;
337 PointerRNA *filter_ptr = UI_panel_custom_data_get(panel);
339
340 int current_index = BLI_findindex(row_filters, filter);
341 BLI_assert(current_index >= 0);
342 BLI_assert(new_index >= 0);
343
344 BLI_listbase_link_move(row_filters, filter, new_index - current_index);
345}
346
347static short get_filter_expand_flag(const bContext * /*C*/, Panel *panel)
348{
349 PointerRNA *filter_ptr = UI_panel_custom_data_get(panel);
351
352 return short(filter->flag) & SPREADSHEET_ROW_FILTER_UI_EXPAND;
353}
354
355static void set_filter_expand_flag(const bContext * /*C*/, Panel *panel, short expand_flag)
356{
357 PointerRNA *filter_ptr = UI_panel_custom_data_get(panel);
359
363}
364
366{
367 {
368 PanelType *panel_type = MEM_callocN<PanelType>(__func__);
369 STRNCPY_UTF8(panel_type->idname, "SPREADSHEET_PT_row_filters");
370 STRNCPY_UTF8(panel_type->label, N_("Filters"));
371 STRNCPY_UTF8(panel_type->category, "Filters");
373 panel_type->flag = PANEL_TYPE_NO_HEADER;
375 BLI_addtail(&region_type.paneltypes, panel_type);
376 }
377
378 {
379 PanelType *panel_type = MEM_callocN<PanelType>(__func__);
380 STRNCPY_UTF8(panel_type->idname, "SPREADSHEET_PT_filter");
381 STRNCPY_UTF8(panel_type->label, "");
382 STRNCPY_UTF8(panel_type->category, "Filters");
389 panel_type->reorder = filter_reorder;
390 BLI_addtail(&region_type.paneltypes, panel_type);
391 }
392}
393
394} // namespace blender::ed::spreadsheet
bScreen * CTX_wm_screen(const bContext *C)
ARegion * CTX_wm_region(const bContext *C)
SpaceSpreadsheet * CTX_wm_space_spreadsheet(const bContext *C)
#define BKE_ST_MAXNAME
Definition BKE_screen.hh:72
@ PANEL_TYPE_NO_HEADER
@ PANEL_TYPE_INSTANCED
@ PANEL_TYPE_HEADER_EXPAND
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
int BLI_findindex(const ListBase *listbase, const void *vlink) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:586
#define LISTBASE_FOREACH(type, var, list)
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
void void void bool BLI_listbase_link_move(ListBase *listbase, void *vlink, int step) ATTR_NONNULL()
Definition listbase.cc:436
char * BLI_strncpy_utf8(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
#define STRNCPY_UTF8(dst, src)
#define SET_FLAG_FROM_TEST(value, test, flag)
#define ELEM(...)
#define IFACE_(msgid)
#define BLT_I18NCONTEXT_DEFAULT_BPYRNA
#define MAX_NAME
Definition DNA_defs.h:50
@ SPREADSHEET_ROW_FILTER_BOOL_VALUE
@ SPREADSHEET_ROW_FILTER_UI_EXPAND
@ SPREADSHEET_ROW_FILTER_ENABLED
@ SPREADSHEET_FILTER_ENABLE
eSpreadsheetFilterOperation
@ SPREADSHEET_ROW_FILTER_GREATER
@ SPREADSHEET_ROW_FILTER_EQUAL
@ SPREADSHEET_ROW_FILTER_LESS
eSpreadsheetColumnValueType
@ SPREADSHEET_VALUE_TYPE_INT8
@ SPREADSHEET_VALUE_TYPE_FLOAT
@ SPREADSHEET_VALUE_TYPE_INT32_2D
@ SPREADSHEET_VALUE_TYPE_BYTE_COLOR
@ SPREADSHEET_VALUE_TYPE_UNKNOWN
@ SPREADSHEET_VALUE_TYPE_FLOAT3
@ SPREADSHEET_VALUE_TYPE_BOOL
@ SPREADSHEET_VALUE_TYPE_STRING
@ SPREADSHEET_VALUE_TYPE_INT32_3D
@ SPREADSHEET_VALUE_TYPE_QUATERNION
@ SPREADSHEET_VALUE_TYPE_FLOAT4X4
@ SPREADSHEET_VALUE_TYPE_INT32
@ SPREADSHEET_VALUE_TYPE_BUNDLE_ITEM
@ SPREADSHEET_VALUE_TYPE_INT64
@ SPREADSHEET_VALUE_TYPE_FLOAT2
@ SPREADSHEET_VALUE_TYPE_COLOR
@ SPREADSHEET_VALUE_TYPE_INSTANCES
#define C
Definition RandGen.cpp:29
void UI_panels_free_instanced(const bContext *C, ARegion *region)
void UI_panel_custom_data_set(Panel *panel, PointerRNA *custom_data)
bool UI_panel_list_matches_data(ARegion *region, ListBase *data, uiListPanelIDFromDataFunc panel_idname_func)
PointerRNA * UI_panel_custom_data_get(const Panel *panel)
Panel * UI_panel_add_instanced(const bContext *C, ARegion *region, ListBase *panels, const char *panel_idname, PointerRNA *custom_data)
@ UI_ITEM_R_ICON_ONLY
#define UI_ITEM_NONE
constexpr bool is_empty() const
constexpr const char * data() const
#define filter
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
static void spreadsheet_filter_panel_draw(const bContext *C, Panel *panel)
static std::string operation_string(const eSpreadsheetColumnValueType data_type, const eSpreadsheetFilterOperation operation)
static void set_filter_expand_flag(const bContext *, Panel *panel, short expand_flag)
static std::string value_string(const SpreadsheetRowFilter &row_filter, const eSpreadsheetColumnValueType data_type)
static void spreadsheet_row_filters_layout(const bContext *C, Panel *panel)
static void filter_panel_id_fn(void *, char *r_name)
static void filter_reorder(bContext *C, Panel *panel, int new_index)
static void spreadsheet_filter_panel_draw_header(const bContext *C, Panel *panel)
SpreadsheetTable * get_active_table(SpaceSpreadsheet &sspreadsheet)
static const SpreadsheetColumn * lookup_visible_column_for_filter(const SpaceSpreadsheet &sspreadsheet, const StringRef column_name)
static short get_filter_expand_flag(const bContext *, Panel *panel)
void register_row_filter_panels(ARegionType &region_type)
void RNA_int_set(PointerRNA *ptr, const char *name, int value)
PointerRNA RNA_pointer_create_discrete(ID *id, StructRNA *type, void *data)
ListBase paneltypes
ListBase panels
void * first
void(* reorder)(bContext *C, Panel *pa, int new_index)
void(* set_list_data_expand_flag)(const bContext *C, Panel *pa, short expand_flag)
void(* draw)(const bContext *C, Panel *panel)
char idname[BKE_ST_MAXNAME]
char translation_context[BKE_ST_MAXNAME]
char category[BKE_ST_MAXNAME]
char label[BKE_ST_MAXNAME]
short(* get_list_data_expand_flag)(const bContext *C, Panel *pa)
void(* draw_header)(const bContext *C, Panel *panel)
struct PanelType * type
struct uiLayout * layout
struct Panel * next
void * data
Definition RNA_types.hh:53
SpreadsheetColumn ** columns
void use_property_decorate_set(bool is_sep)
void label(blender::StringRef name, int icon)
void active_set(bool active)
void separator(float factor=1.0f, LayoutSeparatorType type=LayoutSeparatorType::Auto)
uiLayout & row(bool align)
void emboss_set(blender::ui::EmbossType emboss)
PointerRNA op(wmOperatorType *ot, std::optional< blender::StringRef > name, int icon, blender::wm::OpCallContext context, eUI_Item_Flag flag)
void use_property_split_set(bool value)
void prop(PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, std::optional< blender::StringRef > name_opt, int icon, std::optional< blender::StringRef > placeholder=std::nullopt)
#define N_(msgid)