From f9aea19d98908be450f228a35bb6098e7e3e4b03 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 18 Jun 2021 16:33:02 -0500 Subject: Spreadsheet Editor: Row Filters This patch adds support for filtering rows based on rules and values. Filters will work for any attribute data source, they are a property of the spreadsheet rather than of the attribute system. The properties displayed in the row filter can depend on data type of the currently visible column with that name. If the name is no longer visible, the row filter filter is grayed out, but it will remember the value until a column with its name is visible again. Note: The comments in `screen.c` combined with tagging the sidebar for redraw after the main region point to a lack of understanding or technical debt, that is a point to improve in the future. **Future Improvements** * T89272: A search menu for visible columns when adding a new filter. * T89273: Possibly a "Range" operation. Differential Revision: https://developer.blender.org/D10959 --- source/blender/editors/include/ED_screen.h | 1 + source/blender/editors/interface/interface_panel.c | 2 +- source/blender/editors/screen/screen_ops.c | 5 + .../editors/space_spreadsheet/CMakeLists.txt | 5 + .../editors/space_spreadsheet/space_spreadsheet.cc | 92 +++++- .../space_spreadsheet/spreadsheet_column.cc | 13 + .../space_spreadsheet/spreadsheet_column.hh | 3 + .../space_spreadsheet/spreadsheet_column_values.hh | 23 +- .../space_spreadsheet/spreadsheet_data_source.hh | 9 + .../spreadsheet_data_source_geometry.cc | 122 ++++--- .../spreadsheet_data_source_geometry.hh | 3 +- .../editors/space_spreadsheet/spreadsheet_ops.cc | 75 +++++ .../space_spreadsheet/spreadsheet_row_filter.cc | 366 +++++++++++++++++++++ .../space_spreadsheet/spreadsheet_row_filter.hh | 35 ++ .../space_spreadsheet/spreadsheet_row_filter_ui.cc | 347 +++++++++++++++++++ .../space_spreadsheet/spreadsheet_row_filter_ui.hh | 21 ++ 16 files changed, 1052 insertions(+), 70 deletions(-) create mode 100644 source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc create mode 100644 source/blender/editors/space_spreadsheet/spreadsheet_row_filter.hh create mode 100644 source/blender/editors/space_spreadsheet/spreadsheet_row_filter_ui.cc create mode 100644 source/blender/editors/space_spreadsheet/spreadsheet_row_filter_ui.hh (limited to 'source/blender/editors') diff --git a/source/blender/editors/include/ED_screen.h b/source/blender/editors/include/ED_screen.h index a611fb50e4e..823050b46f7 100644 --- a/source/blender/editors/include/ED_screen.h +++ b/source/blender/editors/include/ED_screen.h @@ -317,6 +317,7 @@ bool ED_operator_animview_active(struct bContext *C); bool ED_operator_outliner_active(struct bContext *C); bool ED_operator_outliner_active_no_editobject(struct bContext *C); bool ED_operator_file_active(struct bContext *C); +bool ED_operator_spreadsheet_active(struct bContext *C); bool ED_operator_action_active(struct bContext *C); bool ED_operator_buttons_active(struct bContext *C); bool ED_operator_node_active(struct bContext *C); diff --git a/source/blender/editors/interface/interface_panel.c b/source/blender/editors/interface/interface_panel.c index 6505a7cd76a..6694535e3af 100644 --- a/source/blender/editors/interface/interface_panel.c +++ b/source/blender/editors/interface/interface_panel.c @@ -674,7 +674,7 @@ static bool panel_type_context_poll(ARegion *region, const PanelType *panel_type, const char *context) { - if (UI_panel_category_is_visible(region)) { + if (!BLI_listbase_is_empty(®ion->panels_category)) { return STREQ(panel_type->category, UI_panel_category_active_get(region, false)); } diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c index 88227207a24..f0e12ca60e9 100644 --- a/source/blender/editors/screen/screen_ops.c +++ b/source/blender/editors/screen/screen_ops.c @@ -279,6 +279,11 @@ bool ED_operator_file_active(bContext *C) return ed_spacetype_test(C, SPACE_FILE); } +bool ED_operator_spreadsheet_active(bContext *C) +{ + return ed_spacetype_test(C, SPACE_SPREADSHEET); +} + bool ED_operator_action_active(bContext *C) { return ed_spacetype_test(C, SPACE_ACTION); diff --git a/source/blender/editors/space_spreadsheet/CMakeLists.txt b/source/blender/editors/space_spreadsheet/CMakeLists.txt index b2a0905d4a2..7e3f3db9bc8 100644 --- a/source/blender/editors/space_spreadsheet/CMakeLists.txt +++ b/source/blender/editors/space_spreadsheet/CMakeLists.txt @@ -20,6 +20,7 @@ set(INC ../../blenfont ../../blenkernel ../../blenlib + ../../blentranslation ../../bmesh ../../depsgraph ../../functions @@ -40,6 +41,8 @@ set(SRC spreadsheet_draw.cc spreadsheet_layout.cc spreadsheet_ops.cc + spreadsheet_row_filter.cc + spreadsheet_row_filter_ui.cc spreadsheet_context.hh spreadsheet_cell_value.hh @@ -50,6 +53,8 @@ set(SRC spreadsheet_draw.hh spreadsheet_intern.hh spreadsheet_layout.hh + spreadsheet_row_filter.hh + spreadsheet_row_filter_ui.hh ) set(LIB diff --git a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc index 1f0b5d5d13e..8c42f28b5f4 100644 --- a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc +++ b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc @@ -49,6 +49,8 @@ #include "spreadsheet_data_source_geometry.hh" #include "spreadsheet_intern.hh" #include "spreadsheet_layout.hh" +#include "spreadsheet_row_filter.hh" +#include "spreadsheet_row_filter_ui.hh" using namespace blender; using namespace blender::ed::spreadsheet; @@ -59,6 +61,8 @@ static SpaceLink *spreadsheet_create(const ScrArea *UNUSED(area), const Scene *U "spreadsheet space"); spreadsheet_space->spacetype = SPACE_SPREADSHEET; + spreadsheet_space->filter_flag = SPREADSHEET_FILTER_ENABLE; + { /* Header. */ ARegion *region = (ARegion *)MEM_callocN(sizeof(ARegion), "spreadsheet header"); @@ -75,6 +79,15 @@ static SpaceLink *spreadsheet_create(const ScrArea *UNUSED(area), const Scene *U region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_TOP : RGN_ALIGN_BOTTOM; } + { + /* Properties region. */ + ARegion *region = (ARegion *)MEM_callocN(sizeof(ARegion), "spreadsheet right region"); + BLI_addtail(&spreadsheet_space->regionbase, region); + region->regiontype = RGN_TYPE_UI; + region->alignment = RGN_ALIGN_RIGHT; + region->flag = RGN_FLAG_HIDDEN; + } + { /* Main window. */ ARegion *region = (ARegion *)MEM_callocN(sizeof(ARegion), "spreadsheet main region"); @@ -88,8 +101,12 @@ static SpaceLink *spreadsheet_create(const ScrArea *UNUSED(area), const Scene *U static void spreadsheet_free(SpaceLink *sl) { SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl; + MEM_SAFE_FREE(sspreadsheet->runtime); + LISTBASE_FOREACH_MUTABLE (SpreadsheetRowFilter *, row_filter, &sspreadsheet->row_filters) { + spreadsheet_row_filter_free(row_filter); + } LISTBASE_FOREACH_MUTABLE (SpreadsheetColumn *, column, &sspreadsheet->columns) { spreadsheet_column_free(column); } @@ -113,6 +130,11 @@ static SpaceLink *spreadsheet_duplicate(SpaceLink *sl) SpaceSpreadsheet *sspreadsheet_new = (SpaceSpreadsheet *)MEM_dupallocN(sspreadsheet_old); sspreadsheet_new->runtime = (SpaceSpreadsheet_Runtime *)MEM_dupallocN(sspreadsheet_old->runtime); + BLI_listbase_clear(&sspreadsheet_new->row_filters); + LISTBASE_FOREACH (const SpreadsheetRowFilter *, src_filter, &sspreadsheet_old->row_filters) { + SpreadsheetRowFilter *new_filter = spreadsheet_row_filter_copy(src_filter); + BLI_addtail(&sspreadsheet_new->row_filters, new_filter); + } BLI_listbase_clear(&sspreadsheet_new->columns); LISTBASE_FOREACH (SpreadsheetColumn *, src_column, &sspreadsheet_old->columns) { SpreadsheetColumn *new_column = spreadsheet_column_copy(src_column); @@ -128,8 +150,10 @@ static SpaceLink *spreadsheet_duplicate(SpaceLink *sl) return (SpaceLink *)sspreadsheet_new; } -static void spreadsheet_keymap(wmKeyConfig *UNUSED(keyconf)) +static void spreadsheet_keymap(wmKeyConfig *keyconf) { + /* Entire editor only. */ + WM_keymap_ensure(keyconf, "Spreadsheet Generic", SPACE_SPREADSHEET, 0); } static void spreadsheet_id_remap(ScrArea *UNUSED(area), SpaceLink *slink, ID *old_id, ID *new_id) @@ -160,8 +184,15 @@ static void spreadsheet_main_region_init(wmWindowManager *wm, ARegion *region) UI_view2d_region_reinit(®ion->v2d, V2D_COMMONVIEW_LIST, region->winx, region->winy); - wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "View2D Buttons List", 0, 0); - WM_event_add_keymap_handler(®ion->handlers, keymap); + { + wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "View2D Buttons List", 0, 0); + WM_event_add_keymap_handler(®ion->handlers, keymap); + } + { + wmKeyMap *keymap = WM_keymap_ensure( + wm->defaultconf, "Spreadsheet Generic", SPACE_SPREADSHEET, 0); + WM_event_add_keymap_handler(®ion->handlers, keymap); + } } ID *ED_spreadsheet_get_current_id(struct SpaceSpreadsheet *sspreadsheet) @@ -346,24 +377,14 @@ static void spreadsheet_main_region_draw(const bContext *C, ARegion *region) const ColumnValues *values = scope.add(std::move(values_ptr), __func__); const int width = get_column_width_in_pixels(*values); spreadsheet_layout.columns.append({values, width}); + + spreadsheet_column_assign_runtime_data(column, values->type(), values->name()); } const int tot_rows = data_source->tot_rows(); spreadsheet_layout.index_column_width = get_index_column_width(tot_rows); - spreadsheet_layout.row_indices = IndexRange(tot_rows).as_span(); - - if (const GeometryDataSource *geometry_data_source = dynamic_cast( - data_source.get())) { - Object *object_eval = geometry_data_source->object_eval(); - Object *object_orig = DEG_get_original_object(object_eval); - if (object_orig->type == OB_MESH) { - if (object_orig->mode == OB_MODE_EDIT) { - if (sspreadsheet->filter_flag & SPREADSHEET_FILTER_SELECTED_ONLY) { - spreadsheet_layout.row_indices = geometry_data_source->get_selected_element_indices(); - } - } - } - } + spreadsheet_layout.row_indices = spreadsheet_filter_rows( + *sspreadsheet, spreadsheet_layout, *data_source, scope); sspreadsheet->runtime->tot_columns = spreadsheet_layout.columns.size(); sspreadsheet->runtime->tot_rows = tot_rows; @@ -372,9 +393,11 @@ static void spreadsheet_main_region_draw(const bContext *C, ARegion *region) std::unique_ptr drawer = spreadsheet_drawer_from_layout(spreadsheet_layout); draw_spreadsheet_in_region(C, region, *drawer); - /* Tag footer for redraw, because the main region updates data for the footer. */ + /* Tag other regions for redraw, because the main region updates data for them. */ ARegion *footer = BKE_area_find_region_type(CTX_wm_area(C), RGN_TYPE_FOOTER); ED_region_tag_redraw(footer); + ARegion *sidebar = BKE_area_find_region_type(CTX_wm_area(C), RGN_TYPE_UI); + ED_region_tag_redraw(sidebar); } static void spreadsheet_main_region_listener(const wmRegionListenerParams *params) @@ -511,6 +534,24 @@ static void spreadsheet_footer_region_listener(const wmRegionListenerParams *UNU { } +static void spreadsheet_sidebar_init(wmWindowManager *wm, ARegion *region) +{ + UI_panel_category_active_set_default(region, "Filters"); + ED_region_panels_init(wm, region); + + wmKeyMap *keymap = WM_keymap_ensure( + wm->defaultconf, "Spreadsheet Generic", SPACE_SPREADSHEET, 0); + WM_event_add_keymap_handler(®ion->handlers, keymap); +} + +static void spreadsheet_right_region_free(ARegion *UNUSED(region)) +{ +} + +static void spreadsheet_right_region_listener(const wmRegionListenerParams *UNUSED(params)) +{ +} + void ED_spacetype_spreadsheet(void) { SpaceType *st = (SpaceType *)MEM_callocN(sizeof(SpaceType), "spacetype spreadsheet"); @@ -563,5 +604,20 @@ void ED_spacetype_spreadsheet(void) art->listener = spreadsheet_footer_region_listener; BLI_addhead(&st->regiontypes, art); + /* regions: right panel buttons */ + art = (ARegionType *)MEM_callocN(sizeof(ARegionType), "spacetype spreadsheet right region"); + art->regionid = RGN_TYPE_UI; + art->prefsizex = UI_SIDEBAR_PANEL_WIDTH; + art->keymapflag = ED_KEYMAP_UI | ED_KEYMAP_FRAMES; + + art->init = spreadsheet_sidebar_init; + art->layout = ED_region_panels_layout; + art->draw = ED_region_panels_draw; + art->free = spreadsheet_right_region_free; + art->listener = spreadsheet_right_region_listener; + BLI_addhead(&st->regiontypes, art); + + register_row_filter_panels(*art); + BKE_spacetype_register(st); } diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_column.cc b/source/blender/editors/space_spreadsheet/spreadsheet_column.cc index de40545fdae..ee08c86b29f 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_column.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_column.cc @@ -56,16 +56,29 @@ SpreadsheetColumn *spreadsheet_column_new(SpreadsheetColumnID *column_id) return column; } +void spreadsheet_column_assign_runtime_data(SpreadsheetColumn *column, + const eSpreadsheetColumnValueType data_type, + const StringRefNull display_name) +{ + column->data_type = data_type; + MEM_SAFE_FREE(column->display_name); + column->display_name = BLI_strdup(display_name.c_str()); +} + SpreadsheetColumn *spreadsheet_column_copy(const SpreadsheetColumn *src_column) { SpreadsheetColumnID *new_column_id = spreadsheet_column_id_copy(src_column->id); SpreadsheetColumn *new_column = spreadsheet_column_new(new_column_id); + if (src_column->display_name != nullptr) { + new_column->display_name = BLI_strdup(src_column->display_name); + } return new_column; } void spreadsheet_column_free(SpreadsheetColumn *column) { spreadsheet_column_id_free(column->id); + MEM_SAFE_FREE(column->display_name); MEM_freeN(column); } diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_column.hh b/source/blender/editors/space_spreadsheet/spreadsheet_column.hh index bb245851d55..1a03278acad 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_column.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_column.hh @@ -43,6 +43,9 @@ void spreadsheet_column_id_free(SpreadsheetColumnID *column_id); SpreadsheetColumn *spreadsheet_column_new(SpreadsheetColumnID *column_id); SpreadsheetColumn *spreadsheet_column_copy(const SpreadsheetColumn *src_column); +void spreadsheet_column_assign_runtime_data(SpreadsheetColumn *column, + const eSpreadsheetColumnValueType data_type, + const StringRefNull display_name); void spreadsheet_column_free(SpreadsheetColumn *column); } // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_column_values.hh b/source/blender/editors/space_spreadsheet/spreadsheet_column_values.hh index 373c988a41c..68370cf6a44 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_column_values.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_column_values.hh @@ -16,6 +16,8 @@ #pragma once +#include "DNA_space_types.h" + #include "BLI_string_ref.hh" #include "spreadsheet_cell_value.hh" @@ -28,11 +30,13 @@ namespace blender::ed::spreadsheet { */ class ColumnValues { protected: + eSpreadsheetColumnValueType type_; std::string name_; int size_; public: - ColumnValues(std::string name, const int size) : name_(std::move(name)), size_(size) + ColumnValues(const eSpreadsheetColumnValueType type, std::string name, const int size) + : type_(type), name_(std::move(name)), size_(size) { } @@ -40,6 +44,11 @@ class ColumnValues { virtual void get_value(int index, CellValue &r_cell_value) const = 0; + eSpreadsheetColumnValueType type() const + { + return type_; + } + StringRefNull name() const { return name_; @@ -60,8 +69,11 @@ template class LambdaColumnValues : public ColumnValues { GetValueF get_value_; public: - LambdaColumnValues(std::string name, int size, GetValueF get_value) - : ColumnValues(std::move(name), size), get_value_(std::move(get_value)) + LambdaColumnValues(const eSpreadsheetColumnValueType type, + std::string name, + int size, + GetValueF get_value) + : ColumnValues(type, std::move(name), size), get_value_(std::move(get_value)) { } @@ -73,13 +85,14 @@ template class LambdaColumnValues : public ColumnValues { /* Utility function that simplifies creating a spreadsheet column from a lambda function. */ template -std::unique_ptr column_values_from_function(std::string name, +std::unique_ptr column_values_from_function(const eSpreadsheetColumnValueType type, + std::string name, const int size, GetValueF get_value, const float default_width = 0.0f) { std::unique_ptr column_values = std::make_unique>( - std::move(name), size, std::move(get_value)); + type, std::move(name), size, std::move(get_value)); column_values->default_width = default_width; return column_values; } diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source.hh b/source/blender/editors/space_spreadsheet/spreadsheet_data_source.hh index de47109a144..fad1770e621 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source.hh @@ -53,6 +53,15 @@ class DataSource { return {}; } + /** + * Returns true iff the data source has the ability to limit visible rows + * by user interface selection status. + */ + virtual bool has_selection_filter() const + { + return false; + } + /** * Returns the number of rows in columns returned by #get_column_values. */ diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc index 452885959f6..77248254d7b 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc @@ -69,28 +69,35 @@ std::unique_ptr GeometryDataSource::get_column_values( const CustomDataType type = bke::cpp_type_to_custom_data_type(varray->type()); switch (type) { case CD_PROP_FLOAT: - return column_values_from_function( - column_id.name, domain_size, [varray](int index, CellValue &r_cell_value) { - float value; - varray->get(index, &value); - r_cell_value.value_float = value; - }); + return column_values_from_function(SPREADSHEET_VALUE_TYPE_FLOAT, + column_id.name, + domain_size, + [varray](int index, CellValue &r_cell_value) { + float value; + varray->get(index, &value); + r_cell_value.value_float = value; + }); case CD_PROP_INT32: - return column_values_from_function( - column_id.name, domain_size, [varray](int index, CellValue &r_cell_value) { - int value; - varray->get(index, &value); - r_cell_value.value_int = value; - }); + return column_values_from_function(SPREADSHEET_VALUE_TYPE_INT32, + column_id.name, + domain_size, + [varray](int index, CellValue &r_cell_value) { + int value; + varray->get(index, &value); + r_cell_value.value_int = value; + }); case CD_PROP_BOOL: - return column_values_from_function( - column_id.name, domain_size, [varray](int index, CellValue &r_cell_value) { - bool value; - varray->get(index, &value); - r_cell_value.value_bool = value; - }); + return column_values_from_function(SPREADSHEET_VALUE_TYPE_BOOL, + column_id.name, + domain_size, + [varray](int index, CellValue &r_cell_value) { + bool value; + varray->get(index, &value); + r_cell_value.value_bool = value; + }); case CD_PROP_FLOAT2: { return column_values_from_function( + SPREADSHEET_VALUE_TYPE_FLOAT2, column_id.name, domain_size, [varray](int index, CellValue &r_cell_value) { @@ -102,6 +109,7 @@ std::unique_ptr GeometryDataSource::get_column_values( } case CD_PROP_FLOAT3: { return column_values_from_function( + SPREADSHEET_VALUE_TYPE_FLOAT3, column_id.name, domain_size, [varray](int index, CellValue &r_cell_value) { @@ -113,6 +121,7 @@ std::unique_ptr GeometryDataSource::get_column_values( } case CD_PROP_COLOR: { return column_values_from_function( + SPREADSHEET_VALUE_TYPE_COLOR, column_id.name, domain_size, [varray](int index, CellValue &r_cell_value) { @@ -137,55 +146,63 @@ using IsVertexSelectedFn = FunctionRef; static void get_selected_vertex_indices(const Mesh &mesh, const IsVertexSelectedFn is_vertex_selected_fn, - Vector &r_vertex_indices) + MutableSpan selection) { for (const int i : IndexRange(mesh.totvert)) { - if (is_vertex_selected_fn(i)) { - r_vertex_indices.append(i); + if (!selection[i]) { + continue; + } + if (!is_vertex_selected_fn(i)) { + selection[i] = false; } } } static void get_selected_corner_indices(const Mesh &mesh, const IsVertexSelectedFn is_vertex_selected_fn, - Vector &r_corner_indices) + MutableSpan selection) { for (const int i : IndexRange(mesh.totloop)) { const MLoop &loop = mesh.mloop[i]; - if (is_vertex_selected_fn(loop.v)) { - r_corner_indices.append(i); + if (!selection[i]) { + continue; + } + if (!is_vertex_selected_fn(loop.v)) { + selection[i] = false; } } } static void get_selected_face_indices(const Mesh &mesh, const IsVertexSelectedFn is_vertex_selected_fn, - Vector &r_face_indices) + MutableSpan selection) { for (const int poly_index : IndexRange(mesh.totpoly)) { + if (!selection[poly_index]) { + continue; + } const MPoly &poly = mesh.mpoly[poly_index]; - bool is_selected = true; for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { const MLoop &loop = mesh.mloop[loop_index]; if (!is_vertex_selected_fn(loop.v)) { - is_selected = false; + selection[poly_index] = false; break; } } - if (is_selected) { - r_face_indices.append(poly_index); - } } } static void get_selected_edge_indices(const Mesh &mesh, const IsVertexSelectedFn is_vertex_selected_fn, - Vector &r_edge_indices) + MutableSpan selection) { for (const int i : IndexRange(mesh.totedge)) { + if (!selection[i]) { + continue; + } const MEdge &edge = mesh.medge[i]; - if (is_vertex_selected_fn(edge.v1) && is_vertex_selected_fn(edge.v2)) { - r_edge_indices.append(i); + if (!is_vertex_selected_fn(edge.v1) || !is_vertex_selected_fn(edge.v2)) { + selection[i] = false; } } } @@ -193,30 +210,40 @@ static void get_selected_edge_indices(const Mesh &mesh, static void get_selected_indices_on_domain(const Mesh &mesh, const AttributeDomain domain, const IsVertexSelectedFn is_vertex_selected_fn, - Vector &r_indices) + MutableSpan selection) { switch (domain) { case ATTR_DOMAIN_POINT: - return get_selected_vertex_indices(mesh, is_vertex_selected_fn, r_indices); + return get_selected_vertex_indices(mesh, is_vertex_selected_fn, selection); case ATTR_DOMAIN_FACE: - return get_selected_face_indices(mesh, is_vertex_selected_fn, r_indices); + return get_selected_face_indices(mesh, is_vertex_selected_fn, selection); case ATTR_DOMAIN_CORNER: - return get_selected_corner_indices(mesh, is_vertex_selected_fn, r_indices); + return get_selected_corner_indices(mesh, is_vertex_selected_fn, selection); case ATTR_DOMAIN_EDGE: - return get_selected_edge_indices(mesh, is_vertex_selected_fn, r_indices); + return get_selected_edge_indices(mesh, is_vertex_selected_fn, selection); default: return; } } -Span GeometryDataSource::get_selected_element_indices() const +bool GeometryDataSource::has_selection_filter() const +{ + Object *object_orig = DEG_get_original_object(object_eval_); + if (object_orig->type == OB_MESH) { + if (object_orig->mode == OB_MODE_EDIT) { + return true; + } + } + return false; +} + +void GeometryDataSource::apply_selection_filter(MutableSpan rows_included) const { std::lock_guard lock{mutex_}; BLI_assert(object_eval_->mode == OB_MODE_EDIT); BLI_assert(component_->type() == GEO_COMPONENT_TYPE_MESH); Object *object_orig = DEG_get_original_object(object_eval_); - Vector &indices = scope_.construct>(__func__); const MeshComponent *mesh_component = static_cast(component_); const Mesh *mesh_eval = mesh_component->get_for_read(); Mesh *mesh_orig = (Mesh *)object_orig->data; @@ -237,7 +264,7 @@ Span GeometryDataSource::get_selected_element_indices() const BMVert *vert = bm->vtable[i_orig]; return BM_elem_flag_test(vert, BM_ELEM_SELECT); }; - get_selected_indices_on_domain(*mesh_eval, domain_, is_vertex_selected, indices); + get_selected_indices_on_domain(*mesh_eval, domain_, is_vertex_selected, rows_included); } else if (mesh_eval->totvert == bm->totvert) { /* Use a simple heuristic to match original vertices to evaluated ones. */ @@ -245,10 +272,8 @@ Span GeometryDataSource::get_selected_element_indices() const BMVert *vert = bm->vtable[vertex_index]; return BM_elem_flag_test(vert, BM_ELEM_SELECT); }; - get_selected_indices_on_domain(*mesh_eval, domain_, is_vertex_selected, indices); + get_selected_indices_on_domain(*mesh_eval, domain_, is_vertex_selected, rows_included); } - - return indices; } void InstancesDataSource::foreach_default_column_ids( @@ -279,7 +304,10 @@ std::unique_ptr InstancesDataSource::get_column_values( Span reference_handles = component_->instance_reference_handles(); Span references = component_->references(); std::unique_ptr values = column_values_from_function( - "Name", size, [reference_handles, references](int index, CellValue &r_cell_value) { + SPREADSHEET_VALUE_TYPE_INSTANCES, + "Name", + size, + [reference_handles, references](int index, CellValue &r_cell_value) { const InstanceReference &reference = references[reference_handles[index]]; switch (reference.type()) { case InstanceReference::Type::Object: { @@ -303,6 +331,7 @@ std::unique_ptr InstancesDataSource::get_column_values( Span transforms = component_->instance_transforms(); if (STREQ(column_id.name, "Position")) { return column_values_from_function( + SPREADSHEET_VALUE_TYPE_FLOAT3, column_id.name, size, [transforms](int index, CellValue &r_cell_value) { @@ -312,6 +341,7 @@ std::unique_ptr InstancesDataSource::get_column_values( } if (STREQ(column_id.name, "Rotation")) { return column_values_from_function( + SPREADSHEET_VALUE_TYPE_FLOAT3, column_id.name, size, [transforms](int index, CellValue &r_cell_value) { @@ -321,6 +351,7 @@ std::unique_ptr InstancesDataSource::get_column_values( } if (STREQ(column_id.name, "Scale")) { return column_values_from_function( + SPREADSHEET_VALUE_TYPE_FLOAT3, column_id.name, size, [transforms](int index, CellValue &r_cell_value) { @@ -332,6 +363,7 @@ std::unique_ptr InstancesDataSource::get_column_values( if (STREQ(column_id.name, "ID")) { /* Make the column a bit wider by default, since the IDs tend to be large numbers. */ return column_values_from_function( + SPREADSHEET_VALUE_TYPE_INT32, column_id.name, size, [ids](int index, CellValue &r_cell_value) { r_cell_value.value_int = ids[index]; }, diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh index 273d39f27bf..d1b5dc6845e 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh @@ -58,7 +58,8 @@ class GeometryDataSource : public DataSource { return object_eval_; } - Span get_selected_element_indices() const; + bool has_selection_filter() const override; + void apply_selection_filter(MutableSpan rows_included) const; void foreach_default_column_ids( FunctionRef fn) const override; diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_ops.cc b/source/blender/editors/space_spreadsheet/spreadsheet_ops.cc index 770bd207e8d..fcbc37346e6 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_ops.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_ops.cc @@ -14,8 +14,83 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BLI_listbase.h" + +#include "MEM_guardedalloc.h" + +#include "BKE_context.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "ED_screen.h" + +#include "WM_api.h" +#include "WM_types.h" + #include "spreadsheet_intern.hh" +#include "spreadsheet_row_filter.hh" + +using namespace blender::ed::spreadsheet; + +static int row_filter_add_exec(bContext *C, wmOperator *UNUSED(op)) +{ + SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); + + SpreadsheetRowFilter *row_filter = spreadsheet_row_filter_new(); + BLI_addtail(&sspreadsheet->row_filters, row_filter); + + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_SPREADSHEET, sspreadsheet); + + return OPERATOR_FINISHED; +} + +static void SPREADSHEET_OT_add_row_filter_rule(wmOperatorType *ot) +{ + ot->name = "Add Row Filter"; + ot->description = "Add a filter to remove rows from the displayed data"; + ot->idname = "SPREADSHEET_OT_add_row_filter_rule"; + + ot->exec = row_filter_add_exec; + ot->poll = ED_operator_spreadsheet_active; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +static int row_filter_remove_exec(bContext *C, wmOperator *op) +{ + SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); + + SpreadsheetRowFilter *row_filter = (SpreadsheetRowFilter *)BLI_findlink( + &sspreadsheet->row_filters, RNA_int_get(op->ptr, "index")); + if (row_filter == nullptr) { + return OPERATOR_CANCELLED; + } + + BLI_remlink(&sspreadsheet->row_filters, row_filter); + spreadsheet_row_filter_free(row_filter); + + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_SPREADSHEET, sspreadsheet); + + return OPERATOR_FINISHED; +} + +static void SPREADSHEET_OT_remove_row_filter_rule(wmOperatorType *ot) +{ + ot->name = "Remove Row Filter"; + ot->description = "Remove a row filter from the rules"; + ot->idname = "SPREADSHEET_OT_remove_row_filter_rule"; + + ot->exec = row_filter_remove_exec; + ot->poll = ED_operator_spreadsheet_active; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "", 0, INT_MAX); +} void spreadsheet_operatortypes() { + WM_operatortype_append(SPREADSHEET_OT_add_row_filter_rule); + WM_operatortype_append(SPREADSHEET_OT_remove_row_filter_rule); } diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc new file mode 100644 index 00000000000..ae336edfead --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc @@ -0,0 +1,366 @@ +/* + * 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. + */ + +#include + +#include "BLI_listbase.h" + +#include "DNA_collection_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "DEG_depsgraph_query.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "RNA_access.h" + +#include "spreadsheet_intern.hh" + +#include "spreadsheet_data_source_geometry.hh" +#include "spreadsheet_intern.hh" +#include "spreadsheet_layout.hh" +#include "spreadsheet_row_filter.hh" + +namespace blender::ed::spreadsheet { + +template +static void apply_filter_operation(const ColumnValues &values, + OperationFn check_fn, + MutableSpan rows_included) +{ + for (const int i : rows_included.index_range()) { + if (!rows_included[i]) { + continue; + } + CellValue cell_value; + values.get_value(i, cell_value); + if (!check_fn(cell_value)) { + rows_included[i] = false; + } + } +} + +static void apply_row_filter(const SpreadsheetLayout &spreadsheet_layout, + const SpreadsheetRowFilter &row_filter, + MutableSpan rows_included) +{ + for (const ColumnLayout &column : spreadsheet_layout.columns) { + const ColumnValues &values = *column.values; + if (values.name() != row_filter.column_name) { + continue; + } + + switch (values.type()) { + case SPREADSHEET_VALUE_TYPE_INT32: { + const int value = row_filter.value_int; + switch (row_filter.operation) { + case SPREADSHEET_ROW_FILTER_EQUAL: { + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + return *cell_value.value_int == value; + }, + rows_included); + break; + } + case SPREADSHEET_ROW_FILTER_GREATER: { + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + return *cell_value.value_int > value; + }, + rows_included); + break; + } + case SPREADSHEET_ROW_FILTER_LESS: { + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + return *cell_value.value_int < value; + }, + rows_included); + break; + } + } + break; + } + case SPREADSHEET_VALUE_TYPE_FLOAT: { + const float value = row_filter.value_float; + switch (row_filter.operation) { + case SPREADSHEET_ROW_FILTER_EQUAL: { + const float threshold = row_filter.threshold; + apply_filter_operation( + values, + [value, threshold](const CellValue &cell_value) -> bool { + return std::abs(*cell_value.value_float - value) < threshold; + }, + rows_included); + break; + } + case SPREADSHEET_ROW_FILTER_GREATER: { + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + return *cell_value.value_float > value; + }, + rows_included); + break; + } + case SPREADSHEET_ROW_FILTER_LESS: { + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + return *cell_value.value_float < value; + }, + rows_included); + break; + } + } + break; + } + case SPREADSHEET_VALUE_TYPE_FLOAT2: { + const float2 value = row_filter.value_float2; + switch (row_filter.operation) { + case SPREADSHEET_ROW_FILTER_EQUAL: { + const float threshold_squared = row_filter.threshold * row_filter.threshold; + apply_filter_operation( + values, + [value, threshold_squared](const CellValue &cell_value) -> bool { + return float2::distance_squared(*cell_value.value_float2, value) < + threshold_squared; + }, + rows_included); + break; + } + case SPREADSHEET_ROW_FILTER_GREATER: { + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + return cell_value.value_float2->x > value.x && + cell_value.value_float2->y > value.y; + }, + rows_included); + break; + } + case SPREADSHEET_ROW_FILTER_LESS: { + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + return cell_value.value_float2->x < value.x && + cell_value.value_float2->y < value.y; + }, + rows_included); + break; + } + } + break; + } + case SPREADSHEET_VALUE_TYPE_FLOAT3: { + const float3 value = row_filter.value_float3; + switch (row_filter.operation) { + case SPREADSHEET_ROW_FILTER_EQUAL: { + const float threshold_squared = row_filter.threshold * row_filter.threshold; + apply_filter_operation( + values, + [value, threshold_squared](const CellValue &cell_value) -> bool { + return float3::distance_squared(*cell_value.value_float3, value) < + threshold_squared; + }, + rows_included); + break; + } + case SPREADSHEET_ROW_FILTER_GREATER: { + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + return cell_value.value_float3->x > value.x && + cell_value.value_float3->y > value.y && + cell_value.value_float3->z > value.z; + }, + rows_included); + break; + } + case SPREADSHEET_ROW_FILTER_LESS: { + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + return cell_value.value_float3->x < value.x && + cell_value.value_float3->y < value.y && + cell_value.value_float3->z < value.z; + }, + rows_included); + break; + } + } + break; + } + case SPREADSHEET_VALUE_TYPE_COLOR: { + const ColorGeometry4f value = row_filter.value_color; + switch (row_filter.operation) { + case SPREADSHEET_ROW_FILTER_EQUAL: { + const float threshold_squared = row_filter.threshold * row_filter.threshold; + apply_filter_operation( + values, + [value, threshold_squared](const CellValue &cell_value) -> bool { + return len_squared_v4v4(value, *cell_value.value_color) < threshold_squared; + }, + rows_included); + break; + } + } + break; + } + case SPREADSHEET_VALUE_TYPE_BOOL: { + const bool value = (row_filter.flag & SPREADSHEET_ROW_FILTER_BOOL_VALUE) != 0; + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + return *cell_value.value_bool == value; + }, + rows_included); + break; + } + case SPREADSHEET_VALUE_TYPE_INSTANCES: { + const StringRef value = row_filter.value_string; + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + const ID *id = nullptr; + if (cell_value.value_object) { + id = &cell_value.value_object->object->id; + } + else if (cell_value.value_collection) { + id = &cell_value.value_collection->collection->id; + } + if (id == nullptr) { + return false; + } + + return value == id->name + 2; + }, + rows_included); + break; + } + default: + break; + } + + /* Only one column should have this name. */ + break; + } +} + +static void index_vector_from_bools(Span selection, Vector &indices) +{ + for (const int i : selection.index_range()) { + if (selection[i]) { + indices.append(i); + } + } +} + +static bool use_row_filters(const SpaceSpreadsheet &sspreadsheet) +{ + if (!(sspreadsheet.filter_flag & SPREADSHEET_FILTER_ENABLE)) { + return false; + } + if (BLI_listbase_is_empty(&sspreadsheet.row_filters)) { + return false; + } + return true; +} + +static bool use_selection_filter(const SpaceSpreadsheet &sspreadsheet, + const DataSource &data_source) +{ + if (!(sspreadsheet.filter_flag & SPREADSHEET_FILTER_SELECTED_ONLY)) { + return false; + } + if (!data_source.has_selection_filter()) { + return false; + } + return true; +} + +Span spreadsheet_filter_rows(const SpaceSpreadsheet &sspreadsheet, + const SpreadsheetLayout &spreadsheet_layout, + const DataSource &data_source, + ResourceScope &scope) +{ + const int tot_rows = data_source.tot_rows(); + + const bool use_selection = use_selection_filter(sspreadsheet, data_source); + const bool use_filters = use_row_filters(sspreadsheet); + + /* Avoid allocating an array if no row filtering is necessary. */ + if (!(use_filters || use_selection)) { + return IndexRange(tot_rows).as_span(); + } + + Array rows_included(tot_rows, true); + + if (use_filters) { + LISTBASE_FOREACH (const SpreadsheetRowFilter *, row_filter, &sspreadsheet.row_filters) { + if (row_filter->flag & SPREADSHEET_ROW_FILTER_ENABLED) { + apply_row_filter(spreadsheet_layout, *row_filter, rows_included); + } + } + } + + if (use_selection) { + const GeometryDataSource *geometry_data_source = dynamic_cast( + &data_source); + geometry_data_source->apply_selection_filter(rows_included); + } + + Vector &indices = scope.construct>(__func__); + index_vector_from_bools(rows_included, indices); + + return indices; +} + +SpreadsheetRowFilter *spreadsheet_row_filter_new() +{ + SpreadsheetRowFilter *row_filter = (SpreadsheetRowFilter *)MEM_callocN( + sizeof(SpreadsheetRowFilter), __func__); + row_filter->flag = (SPREADSHEET_ROW_FILTER_UI_EXPAND | SPREADSHEET_ROW_FILTER_ENABLED); + row_filter->operation = SPREADSHEET_ROW_FILTER_LESS; + row_filter->threshold = 0.01f; + row_filter->column_name[0] = '\0'; + + return row_filter; +} + +SpreadsheetRowFilter *spreadsheet_row_filter_copy(const SpreadsheetRowFilter *src_row_filter) +{ + SpreadsheetRowFilter *new_filter = spreadsheet_row_filter_new(); + + memcpy(new_filter, src_row_filter, sizeof(SpreadsheetRowFilter)); + new_filter->next = nullptr; + new_filter->prev = nullptr; + + return new_filter; +} + +void spreadsheet_row_filter_free(SpreadsheetRowFilter *row_filter) +{ + MEM_SAFE_FREE(row_filter->value_string); + MEM_freeN(row_filter); +} + +} // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.hh b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.hh new file mode 100644 index 00000000000..4835a73b06b --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.hh @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#pragma once + +#include "BLI_resource_scope.hh" + +#include "spreadsheet_data_source.hh" +#include "spreadsheet_layout.hh" + +namespace blender::ed::spreadsheet { + +Span spreadsheet_filter_rows(const SpaceSpreadsheet &sspreadsheet, + const SpreadsheetLayout &spreadsheet_layout, + const DataSource &data_source, + ResourceScope &scope); + +SpreadsheetRowFilter *spreadsheet_row_filter_new(); +SpreadsheetRowFilter *spreadsheet_row_filter_copy(const SpreadsheetRowFilter *src_row_filter); +void spreadsheet_row_filter_free(SpreadsheetRowFilter *column); + +} // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_row_filter_ui.cc b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter_ui.cc new file mode 100644 index 00000000000..dbd2ef157af --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter_ui.cc @@ -0,0 +1,347 @@ +/* + * 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. + */ + +#include + +#include "BLI_listbase.h" +#include "BLI_string.h" +#include "BLI_string_ref.hh" + +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "BKE_screen.h" + +#include "RNA_access.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "BLT_translation.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "spreadsheet_column.hh" +#include "spreadsheet_intern.hh" +#include "spreadsheet_row_filter.hh" +#include "spreadsheet_row_filter_ui.hh" + +using namespace blender; +using namespace blender::ed::spreadsheet; + +static void filter_panel_id_fn(void *UNUSED(row_filter_v), char *r_name) +{ + /* All row filters use the same panel ID. */ + BLI_snprintf(r_name, BKE_ST_MAXNAME, "SPREADSHEET_PT_filter"); +} + +static std::string operation_string(const eSpreadsheetColumnValueType data_type, + const eSpreadsheetFilterOperation operation) +{ + if (ELEM(data_type, + SPREADSHEET_VALUE_TYPE_BOOL, + SPREADSHEET_VALUE_TYPE_INSTANCES, + SPREADSHEET_VALUE_TYPE_COLOR)) { + return "="; + } + + switch (operation) { + case SPREADSHEET_ROW_FILTER_EQUAL: + return "="; + case SPREADSHEET_ROW_FILTER_GREATER: + return ">"; + case SPREADSHEET_ROW_FILTER_LESS: + return "<"; + } + BLI_assert_unreachable(); + return ""; +} + +static std::string value_string(const SpreadsheetRowFilter &row_filter, + const eSpreadsheetColumnValueType data_type) +{ + switch (data_type) { + case SPREADSHEET_VALUE_TYPE_INT32: + return std::to_string(row_filter.value_int); + case SPREADSHEET_VALUE_TYPE_FLOAT: { + std::ostringstream result; + result.precision(3); + result << std::fixed << row_filter.value_float; + return result.str(); + } + case SPREADSHEET_VALUE_TYPE_FLOAT2: { + std::ostringstream result; + result.precision(3); + result << std::fixed << "(" << row_filter.value_float2[0] << ", " + << row_filter.value_float2[1] << ")"; + return result.str(); + } + case SPREADSHEET_VALUE_TYPE_FLOAT3: { + std::ostringstream result; + result.precision(3); + result << std::fixed << "(" << row_filter.value_float3[0] << ", " + << row_filter.value_float3[1] << ", " << row_filter.value_float3[2] << ")"; + return result.str(); + } + case SPREADSHEET_VALUE_TYPE_BOOL: + return (row_filter.flag & SPREADSHEET_ROW_FILTER_BOOL_VALUE) ? IFACE_("True") : + IFACE_("False"); + case SPREADSHEET_VALUE_TYPE_INSTANCES: + if (row_filter.value_string != nullptr) { + return row_filter.value_string; + } + return ""; + case SPREADSHEET_VALUE_TYPE_COLOR: + std::ostringstream result; + result.precision(3); + result << std::fixed << "(" << row_filter.value_color[0] << ", " << row_filter.value_color[1] + << ", " << row_filter.value_color[2] << ", " << row_filter.value_color[3] << ")"; + return result.str(); + } + BLI_assert_unreachable(); + return ""; +} + +static SpreadsheetColumn *lookup_visible_column_for_filter(const SpaceSpreadsheet &sspreadsheet, + const StringRef column_name) +{ + LISTBASE_FOREACH (SpreadsheetColumn *, column, &sspreadsheet.columns) { + if (column->display_name == column_name) { + return column; + } + } + return nullptr; +} + +static void spreadsheet_filter_panel_draw_header(const bContext *C, Panel *panel) +{ + uiLayout *layout = panel->layout; + SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); + PointerRNA *filter_ptr = UI_panel_custom_data_get(panel); + const SpreadsheetRowFilter *filter = (SpreadsheetRowFilter *)filter_ptr->data; + const StringRef column_name = filter->column_name; + const eSpreadsheetFilterOperation operation = (eSpreadsheetFilterOperation)filter->operation; + + const SpreadsheetColumn *column = lookup_visible_column_for_filter(*sspreadsheet, column_name); + if (!(sspreadsheet->filter_flag & SPREADSHEET_FILTER_ENABLE) || + (column == nullptr && !column_name.is_empty())) { + uiLayoutSetActive(layout, false); + } + + uiLayout *row = uiLayoutRow(layout, true); + uiLayoutSetEmboss(row, UI_EMBOSS_NONE); + uiItemR(row, filter_ptr, "enabled", UI_ITEM_R_ICON_ONLY, "", ICON_NONE); + + if (column_name.is_empty()) { + uiItemL(row, IFACE_("Filter"), ICON_NONE); + } + else if (column == nullptr) { + uiItemL(row, column_name.data(), ICON_NONE); + } + else { + const eSpreadsheetColumnValueType data_type = (eSpreadsheetColumnValueType)column->data_type; + std::stringstream ss; + ss << column_name; + ss << " "; + ss << operation_string(data_type, operation); + ss << " "; + ss << value_string(*filter, data_type); + uiItemL(row, ss.str().c_str(), ICON_NONE); + } + + row = uiLayoutRow(layout, true); + uiLayoutSetEmboss(row, UI_EMBOSS_NONE); + const int current_index = BLI_findindex(&sspreadsheet->row_filters, filter); + uiItemIntO(row, "", ICON_X, "SPREADSHEET_OT_remove_row_filter_rule", "index", current_index); + + /* Some padding so the X isn't too close to the drag icon. */ + uiItemS_ex(layout, 0.25f); +} + +static void spreadsheet_filter_panel_draw(const bContext *C, Panel *panel) +{ + uiLayout *layout = panel->layout; + SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); + PointerRNA *filter_ptr = UI_panel_custom_data_get(panel); + SpreadsheetRowFilter *filter = (SpreadsheetRowFilter *)filter_ptr->data; + const StringRef column_name = filter->column_name; + const eSpreadsheetFilterOperation operation = (eSpreadsheetFilterOperation)filter->operation; + + const SpreadsheetColumn *column = lookup_visible_column_for_filter(*sspreadsheet, column_name); + if (!(sspreadsheet->filter_flag & SPREADSHEET_FILTER_ENABLE) || + !(filter->flag & SPREADSHEET_ROW_FILTER_ENABLED) || + (column == nullptr && !column_name.is_empty())) { + uiLayoutSetActive(layout, false); + } + + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + + uiItemR(layout, filter_ptr, "column_name", 0, IFACE_("Column"), ICON_NONE); + + /* Don't draw settings for filters with no corresponding visible column. */ + if (column == nullptr || column_name.is_empty()) { + return; + } + + switch (static_cast(column->data_type)) { + case SPREADSHEET_VALUE_TYPE_INT32: + uiItemR(layout, filter_ptr, "operation", 0, nullptr, ICON_NONE); + uiItemR(layout, filter_ptr, "value_int", 0, IFACE_("Value"), ICON_NONE); + break; + case SPREADSHEET_VALUE_TYPE_FLOAT: + uiItemR(layout, filter_ptr, "operation", 0, nullptr, ICON_NONE); + uiItemR(layout, filter_ptr, "value_float", 0, IFACE_("Value"), ICON_NONE); + if (operation == SPREADSHEET_ROW_FILTER_EQUAL) { + uiItemR(layout, filter_ptr, "threshold", 0, nullptr, ICON_NONE); + } + break; + case SPREADSHEET_VALUE_TYPE_FLOAT2: + uiItemR(layout, filter_ptr, "operation", 0, nullptr, ICON_NONE); + uiItemR(layout, filter_ptr, "value_float2", 0, IFACE_("Value"), ICON_NONE); + if (operation == SPREADSHEET_ROW_FILTER_EQUAL) { + uiItemR(layout, filter_ptr, "threshold", 0, nullptr, ICON_NONE); + } + break; + case SPREADSHEET_VALUE_TYPE_FLOAT3: + uiItemR(layout, filter_ptr, "operation", 0, nullptr, ICON_NONE); + uiItemR(layout, filter_ptr, "value_float3", 0, IFACE_("Value"), ICON_NONE); + if (operation == SPREADSHEET_ROW_FILTER_EQUAL) { + uiItemR(layout, filter_ptr, "threshold", 0, nullptr, ICON_NONE); + } + break; + case SPREADSHEET_VALUE_TYPE_BOOL: + uiItemR(layout, filter_ptr, "value_boolean", 0, IFACE_("Value"), ICON_NONE); + break; + case SPREADSHEET_VALUE_TYPE_INSTANCES: + uiItemR(layout, filter_ptr, "value_string", 0, IFACE_("Value"), ICON_NONE); + break; + case SPREADSHEET_VALUE_TYPE_COLOR: + uiItemR(layout, filter_ptr, "value_color", 0, IFACE_("Value"), ICON_NONE); + uiItemR(layout, filter_ptr, "threshold", 0, nullptr, ICON_NONE); + break; + } +} + +static void spreadsheet_row_filters_layout(const bContext *C, Panel *panel) +{ + uiLayout *layout = panel->layout; + ARegion *region = CTX_wm_region(C); + bScreen *screen = CTX_wm_screen(C); + SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); + ListBase *row_filters = &sspreadsheet->row_filters; + + if (!(sspreadsheet->filter_flag & SPREADSHEET_FILTER_ENABLE)) { + uiLayoutSetActive(layout, false); + } + + uiItemO(layout, nullptr, ICON_ADD, "SPREADSHEET_OT_add_row_filter_rule"); + + const bool panels_match = UI_panel_list_matches_data(region, row_filters, filter_panel_id_fn); + + if (!panels_match) { + UI_panels_free_instanced(C, region); + LISTBASE_FOREACH (SpreadsheetRowFilter *, row_filter, row_filters) { + char panel_idname[MAX_NAME]; + filter_panel_id_fn(row_filter, panel_idname); + + PointerRNA *filter_ptr = (PointerRNA *)MEM_mallocN(sizeof(PointerRNA), "panel customdata"); + RNA_pointer_create(&screen->id, &RNA_SpreadsheetRowFilter, row_filter, filter_ptr); + + UI_panel_add_instanced(C, region, ®ion->panels, panel_idname, filter_ptr); + } + } + else { + /* Assuming there's only one group of instanced panels, update the custom data pointers. */ + Panel *panel = (Panel *)region->panels.first; + LISTBASE_FOREACH (SpreadsheetRowFilter *, row_filter, row_filters) { + + /* Move to the next instanced panel corresponding to the next filter. */ + while ((panel->type == nullptr) || !(panel->type->flag & PANEL_TYPE_INSTANCED)) { + panel = panel->next; + BLI_assert(panel != nullptr); /* There shouldn't be fewer panels than filters. */ + } + + PointerRNA *filter_ptr = (PointerRNA *)MEM_mallocN(sizeof(PointerRNA), "panel customdata"); + RNA_pointer_create(&screen->id, &RNA_SpreadsheetRowFilter, row_filter, filter_ptr); + UI_panel_custom_data_set(panel, filter_ptr); + + panel = panel->next; + } + } +} + +static void filter_reorder(bContext *C, Panel *panel, int new_index) +{ + SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); + ListBase *row_filters = &sspreadsheet->row_filters; + PointerRNA *filter_ptr = UI_panel_custom_data_get(panel); + SpreadsheetRowFilter *filter = (SpreadsheetRowFilter *)filter_ptr->data; + + int current_index = BLI_findindex(row_filters, filter); + BLI_assert(current_index >= 0); + BLI_assert(new_index >= 0); + + BLI_listbase_link_move(row_filters, filter, new_index - current_index); +} + +static short get_filter_expand_flag(const bContext *UNUSED(C), Panel *panel) +{ + PointerRNA *filter_ptr = UI_panel_custom_data_get(panel); + SpreadsheetRowFilter *filter = (SpreadsheetRowFilter *)filter_ptr->data; + + return (short)filter->flag & SPREADSHEET_ROW_FILTER_UI_EXPAND; +} + +static void set_filter_expand_flag(const bContext *UNUSED(C), Panel *panel, short expand_flag) +{ + PointerRNA *filter_ptr = UI_panel_custom_data_get(panel); + SpreadsheetRowFilter *filter = (SpreadsheetRowFilter *)filter_ptr->data; + + SET_FLAG_FROM_TEST(filter->flag, + expand_flag & SPREADSHEET_ROW_FILTER_UI_EXPAND, + SPREADSHEET_ROW_FILTER_UI_EXPAND); +} + +void register_row_filter_panels(ARegionType ®ion_type) +{ + { + PanelType *panel_type = (PanelType *)MEM_callocN(sizeof(PanelType), __func__); + strcpy(panel_type->idname, "SPREADSHEET_PT_row_filters"); + strcpy(panel_type->label, N_("Filters")); + strcpy(panel_type->category, "Filters"); + strcpy(panel_type->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA); + panel_type->flag = PANEL_TYPE_NO_HEADER; + panel_type->draw = spreadsheet_row_filters_layout; + BLI_addtail(®ion_type.paneltypes, panel_type); + } + + { + PanelType *panel_type = (PanelType *)MEM_callocN(sizeof(PanelType), __func__); + strcpy(panel_type->idname, "SPREADSHEET_PT_filter"); + strcpy(panel_type->label, ""); + strcpy(panel_type->category, "Filters"); + strcpy(panel_type->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA); + panel_type->flag = PANEL_TYPE_INSTANCED | PANEL_TYPE_DRAW_BOX | PANEL_TYPE_HEADER_EXPAND; + panel_type->draw_header = spreadsheet_filter_panel_draw_header; + panel_type->draw = spreadsheet_filter_panel_draw; + panel_type->get_list_data_expand_flag = get_filter_expand_flag; + panel_type->set_list_data_expand_flag = set_filter_expand_flag; + panel_type->reorder = filter_reorder; + BLI_addtail(®ion_type.paneltypes, panel_type); + } +} diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_row_filter_ui.hh b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter_ui.hh new file mode 100644 index 00000000000..e22178b63ea --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter_ui.hh @@ -0,0 +1,21 @@ +/* + * 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. + */ + +#pragma once + +struct ARegionType; + +void register_row_filter_panels(ARegionType ®ion_type); -- cgit v1.2.3