diff options
25 files changed, 1381 insertions, 88 deletions
diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index a82304e7614..bd14b2c532c 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -3010,6 +3010,22 @@ def km_clip_dopesheet_editor(_params): return keymap +def km_spreadsheet_generic(_params): + items = [] + keymap = ( + "Spreadsheet Generic", + {"space_type": 'SPREADSHEET', "region_type": 'WINDOW'}, + {"items": items}, + ) + + items.extend([ + *_template_space_region_type_toggle( + sidebar_key={"type": 'N', "value": 'PRESS'}, + ), + ]) + + return keymap + # ------------------------------------------------------------------------------ # Animation @@ -7067,6 +7083,7 @@ def generate_keymaps(params=None): km_image(params), km_node_generic(params), km_node_editor(params), + km_spreadsheet_generic(params), km_info(params), km_file_browser(params), km_file_browser_main(params), diff --git a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py index e56783fcc21..237b7237c27 100644 --- a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py +++ b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py @@ -2144,6 +2144,23 @@ def km_clip_dopesheet_editor(_params): return keymap +def km_spreadsheet_generic(_params): + items = [] + keymap = ( + "Spreadsheet Generic", + {"space_type": 'SPREADSHEET', "region_type": 'WINDOW'}, + {"items": items}, + ) + + items.extend([ + *_template_space_region_type_toggle( + sidebar_key={"type": 'N', "value": 'PRESS'}, + ), + ]) + + return keymap + + # ------------------------------------------------------------------------------ # Animation @@ -4067,6 +4084,7 @@ def generate_keymaps_impl(params=None): km_image(params), km_node_generic(params), km_node_editor(params), + km_spreadsheet_generic(params), km_info(params), km_file_browser(params), km_file_browser_main(params), diff --git a/release/scripts/startup/bl_ui/space_spreadsheet.py b/release/scripts/startup/bl_ui/space_spreadsheet.py index 13e435a7350..360849a0c7a 100644 --- a/release/scripts/startup/bl_ui/space_spreadsheet.py +++ b/release/scripts/startup/bl_ui/space_spreadsheet.py @@ -59,9 +59,12 @@ class SPREADSHEET_HT_header(bpy.types.Header): layout.operator("spreadsheet.toggle_pin", text="", icon=pin_icon, emboss=False) layout.separator_spacer() - - if isinstance(obj, bpy.types.Object) and obj.mode == 'EDIT': - layout.prop(space, "show_only_selected", text="Selected Only") + + row = layout.row(align=True) + sub = row.row(align=True) + sub.active = self.selection_filter_available(space) + sub.prop(space, "show_only_selected", text="") + row.prop(space, "use_filter", toggle=True, icon='FILTER', icon_only=True) def draw_without_context_path(self, layout): layout.label(text="No active context") @@ -102,6 +105,17 @@ class SPREADSHEET_HT_header(bpy.types.Header): def draw_spreadsheet_context_path_icon(self, layout, space, icon='RIGHTARROW_THIN'): layout.prop(space, "display_context_path_collapsed", icon_only=True, emboss=False, icon=icon) + def selection_filter_available(self, space): + root_context = space.context_path[0] + if root_context.type != 'OBJECT': + return False + obj = root_context.object + if obj is None: + return False + if obj.type != 'MESH' or obj.mode != 'EDIT': + return False + return True + classes = ( SPREADSHEET_HT_header, ) diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 6ad910ff8ab..1767077fa45 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -39,7 +39,7 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 3 +#define BLENDER_FILE_SUBVERSION 4 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and show a warning if the file diff --git a/source/blender/blenkernel/intern/screen.c b/source/blender/blenkernel/intern/screen.c index 2d59e978a81..269aeaebe82 100644 --- a/source/blender/blenkernel/intern/screen.c +++ b/source/blender/blenkernel/intern/screen.c @@ -1357,12 +1357,21 @@ static void write_area(BlendWriter *writer, ScrArea *area) } else if (sl->spacetype == SPACE_SPREADSHEET) { BLO_write_struct(writer, SpaceSpreadsheet, sl); - SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl; + + LISTBASE_FOREACH (SpreadsheetRowFilter *, row_filter, &sspreadsheet->row_filters) { + BLO_write_struct(writer, SpreadsheetRowFilter, row_filter); + BLO_write_string(writer, row_filter->value_string); + } + LISTBASE_FOREACH (SpreadsheetColumn *, column, &sspreadsheet->columns) { BLO_write_struct(writer, SpreadsheetColumn, column); BLO_write_struct(writer, SpreadsheetColumnID, column->id); BLO_write_string(writer, column->id->name); + /* While the display name is technically runtime data, we write it here, otherwise the row + * filters might not now their type if their region draws before the main region. + * This would ideally be cleared here. */ + BLO_write_string(writer, column->display_name); } LISTBASE_FOREACH (SpreadsheetContext *, context, &sspreadsheet->context_path) { switch (context->type) { @@ -1743,11 +1752,18 @@ static void direct_link_area(BlendDataReader *reader, ScrArea *area) SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl; sspreadsheet->runtime = NULL; - + BLO_read_list(reader, &sspreadsheet->row_filters); + LISTBASE_FOREACH (SpreadsheetRowFilter *, row_filter, &sspreadsheet->row_filters) { + BLO_read_data_address(reader, &row_filter->value_string); + } BLO_read_list(reader, &sspreadsheet->columns); LISTBASE_FOREACH (SpreadsheetColumn *, column, &sspreadsheet->columns) { BLO_read_data_address(reader, &column->id); BLO_read_data_address(reader, &column->id->name); + /* While the display name is technically runtime data, it is loaded here, otherwise the row + * filters might not now their type if their region draws before the main region. + * This would ideally be cleared here. */ + BLO_read_data_address(reader, &column->display_name); } BLO_read_list(reader, &sspreadsheet->context_path); diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index bdefe51f635..0fe1267b871 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -44,6 +44,8 @@ #include "MEM_guardedalloc.h" +#include "versioning_common.h" + static void sort_linked_ids(Main *bmain) { ListBase *lb; @@ -310,17 +312,37 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } } - /** - * Versioning code until next subversion bump goes here. - * - * \note Be sure to check when bumping the version: - * - "versioning_userdef.c", #blo_do_versions_userdef - * - "versioning_userdef.c", #do_versions_theme - * - * \note Keep this message at the bottom of the function. - */ - { - /* Keep this block, even when empty. */ + if (!MAIN_VERSION_ATLEAST(bmain, 300, 4)) { + /* Add a properties sidebar to the spreadsheet editor. */ + LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) { + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { + if (sl->spacetype == SPACE_SPREADSHEET) { + ListBase *regionbase = (sl == area->spacedata.first) ? &area->regionbase : + &sl->regionbase; + ARegion *new_sidebar = do_versions_add_region_if_not_found( + regionbase, RGN_TYPE_UI, "sidebar for spreadsheet", RGN_TYPE_FOOTER); + if (new_sidebar != NULL) { + new_sidebar->alignment = RGN_ALIGN_RIGHT; + new_sidebar->flag |= RGN_FLAG_HIDDEN; + } + } + } + } + } + + /* Enable spreadsheet filtering in old files without row filters. */ + LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) { + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { + if (sl->spacetype == SPACE_SPREADSHEET) { + SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl; + sspreadsheet->filter_flag |= SPREADSHEET_FILTER_ENABLE; + } + } + } + } + FOREACH_NODETREE_BEGIN (bmain, ntree, id) { if (ntree->type == NTREE_GEOMETRY) { version_node_socket_name(ntree, GEO_NODE_BOUNDING_BOX, "Mesh", "Bounding Box"); @@ -369,4 +391,17 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) BKE_animdata_main_cb(bmain, do_version_bbone_len_scale_animdata_cb, NULL); } } + + /** + * Versioning code until next subversion bump goes here. + * + * \note Be sure to check when bumping the version: + * - "versioning_userdef.c", #blo_do_versions_userdef + * - "versioning_userdef.c", #do_versions_theme + * + * \note Keep this message at the bottom of the function. + */ + { + /* Keep this block, even when empty. */ + } } 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"); @@ -76,6 +80,15 @@ static SpaceLink *spreadsheet_create(const ScrArea *UNUSED(area), const Scene *U } { + /* 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"); BLI_addtail(&spreadsheet_space->regionbase, 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<const GeometryDataSource *>( - 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<SpreadsheetDrawer> 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<typename GetValueF> 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<typename GetValueF> class LambdaColumnValues : public ColumnValues { /* Utility function that simplifies creating a spreadsheet column from a lambda function. */ template<typename GetValueF> -std::unique_ptr<ColumnValues> column_values_from_function(std::string name, +std::unique_ptr<ColumnValues> 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<ColumnValues> column_values = std::make_unique<LambdaColumnValues<GetValueF>>( - 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 @@ -54,6 +54,15 @@ class DataSource { } /** + * 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. */ virtual int tot_rows() const 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<ColumnValues> 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<ColumnValues> 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<ColumnValues> 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<bool(int vertex_index)>; static void get_selected_vertex_indices(const Mesh &mesh, const IsVertexSelectedFn is_vertex_selected_fn, - Vector<int64_t> &r_vertex_indices) + MutableSpan<bool> 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<int64_t> &r_corner_indices) + MutableSpan<bool> 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<int64_t> &r_face_indices) + MutableSpan<bool> 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<int64_t> &r_edge_indices) + MutableSpan<bool> 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<int64_t> &r_indices) + MutableSpan<bool> 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<int64_t> 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<bool> 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<int64_t> &indices = scope_.construct<Vector<int64_t>>(__func__); const MeshComponent *mesh_component = static_cast<const MeshComponent *>(component_); const Mesh *mesh_eval = mesh_component->get_for_read(); Mesh *mesh_orig = (Mesh *)object_orig->data; @@ -237,7 +264,7 @@ Span<int64_t> 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<int64_t> 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<ColumnValues> InstancesDataSource::get_column_values( Span<int> reference_handles = component_->instance_reference_handles(); Span<InstanceReference> references = component_->references(); std::unique_ptr<ColumnValues> 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<ColumnValues> InstancesDataSource::get_column_values( Span<float4x4> 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<ColumnValues> 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<ColumnValues> 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<ColumnValues> 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<int64_t> get_selected_element_indices() const; + bool has_selection_filter() const override; + void apply_selection_filter(MutableSpan<bool> rows_included) const; void foreach_default_column_ids( FunctionRef<void(const SpreadsheetColumnID &)> 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 <cstring> + +#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<typename OperationFn> +static void apply_filter_operation(const ColumnValues &values, + OperationFn check_fn, + MutableSpan<bool> 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<bool> 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<bool> selection, Vector<int64_t> &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<int64_t> 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<bool> 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<const GeometryDataSource *>( + &data_source); + geometry_data_source->apply_selection_filter(rows_included); + } + + Vector<int64_t> &indices = scope.construct<Vector<int64_t>>(__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<int64_t> 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 <cstring> + +#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<eSpreadsheetColumnValueType>(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); diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index 71764d9308c..7804ece9769 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -1874,6 +1874,19 @@ typedef struct SpreadsheetColumn { * #SpreadsheetColumnID in the future for different kinds of ids. */ SpreadsheetColumnID *id; + + /** + * An indicator of the type of values in the column, set at runtime. + * #eSpreadsheetColumnValueType. + */ + uint8_t data_type; + char _pad0[7]; + + /** + * The final column name generated by the data source, also just + * cached at runtime when the data source columns are generated. + */ + char *display_name; } SpreadsheetColumn; /** @@ -1914,6 +1927,9 @@ typedef struct SpaceSpreadsheet { /* List of #SpreadsheetColumn. */ ListBase columns; + /* SpreadsheetRowFilter. */ + ListBase row_filters; + /** * List of #SpreadsheetContext. * This is a path to the data that is displayed in the spreadsheet. @@ -1945,8 +1961,44 @@ typedef enum eSpaceSpreadsheet_Flag { typedef enum eSpaceSpreadsheet_FilterFlag { SPREADSHEET_FILTER_SELECTED_ONLY = (1 << 0), + SPREADSHEET_FILTER_ENABLE = (1 << 1), } eSpaceSpreadsheet_FilterFlag; +typedef struct SpreadsheetRowFilter { + struct SpreadsheetRowFilter *next, *prev; + + char column_name[64]; /* MAX_NAME. */ + + /* eSpreadsheetFilterOperation. */ + uint8_t operation; + /* eSpaceSpreadsheet_RowFilterFlag. */ + uint8_t flag; + + char _pad0[2]; + + int value_int; + char *value_string; + float value_float; + float threshold; + float value_float2[2]; + float value_float3[3]; + float value_color[4]; + + char _pad1[4]; +} SpreadsheetRowFilter; + +typedef enum eSpaceSpreadsheet_RowFilterFlag { + SPREADSHEET_ROW_FILTER_UI_EXPAND = (1 << 0), + SPREADSHEET_ROW_FILTER_BOOL_VALUE = (1 << 1), + SPREADSHEET_ROW_FILTER_ENABLED = (1 << 2), +} eSpaceSpreadsheet_RowFilterFlag; + +typedef enum eSpreadsheetFilterOperation { + SPREADSHEET_ROW_FILTER_EQUAL = 0, + SPREADSHEET_ROW_FILTER_GREATER = 1, + SPREADSHEET_ROW_FILTER_LESS = 2, +} eSpreadsheetFilterOperation; + typedef enum eSpaceSpreadsheet_ObjectEvalState { SPREADSHEET_OBJECT_EVAL_STATE_EVALUATED = 0, SPREADSHEET_OBJECT_EVAL_STATE_ORIGINAL = 1, @@ -1958,6 +2010,16 @@ typedef enum eSpaceSpreadsheet_ContextType { SPREADSHEET_CONTEXT_NODE = 2, } eSpaceSpreadsheet_ContextType; +typedef enum eSpreadsheetColumnValueType { + SPREADSHEET_VALUE_TYPE_BOOL = 0, + SPREADSHEET_VALUE_TYPE_INT32 = 1, + SPREADSHEET_VALUE_TYPE_FLOAT = 2, + SPREADSHEET_VALUE_TYPE_FLOAT2 = 3, + SPREADSHEET_VALUE_TYPE_FLOAT3 = 4, + SPREADSHEET_VALUE_TYPE_COLOR = 5, + SPREADSHEET_VALUE_TYPE_INSTANCES = 6, +} eSpreadsheetColumnValueType; + /** * We can't just use UI_UNIT_X, because it does not take `widget.points` into account, which * modifies the width of text as well. diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index 379e3e77b6e..c691eb3b534 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -615,10 +615,12 @@ extern StructRNA RNA_Spline; extern StructRNA RNA_SplineIKConstraint; extern StructRNA RNA_SplinePoint; extern StructRNA RNA_SpotLight; +extern StructRNA RNA_SpreadsheetColumnID; extern StructRNA RNA_SpreadsheetContext; extern StructRNA RNA_SpreadsheetContextObject; extern StructRNA RNA_SpreadsheetContextModifier; extern StructRNA RNA_SpreadsheetContextNode; +extern StructRNA RNA_SpreadsheetRowFilter; extern StructRNA RNA_Stereo3dDisplay; extern StructRNA RNA_StretchToConstraint; extern StructRNA RNA_StringAttribute; diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index 0af6f85112e..37e2664fa79 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -7411,6 +7411,131 @@ static void rna_def_space_clip(BlenderRNA *brna) RNA_def_property_update(prop, NC_SPACE | ND_SPACE_CLIP, NULL); } +static void rna_def_spreadsheet_column_id(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "SpreadsheetColumnID", NULL); + RNA_def_struct_sdna(srna, "SpreadsheetColumnID"); + RNA_def_struct_ui_text( + srna, "Spreadsheet Column ID", "Data used to identify a spreadsheet column"); + + prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); + RNA_def_property_ui_text(prop, "Column Name", ""); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); +} + +static void rna_def_spreadsheet_column(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + static const EnumPropertyItem data_type_items[] = { + {SPREADSHEET_VALUE_TYPE_INT32, "INT32", ICON_NONE, "Integer", ""}, + {SPREADSHEET_VALUE_TYPE_FLOAT, "FLOAT", ICON_NONE, "Float", ""}, + {SPREADSHEET_VALUE_TYPE_BOOL, "BOOLEAN", ICON_NONE, "Boolean", ""}, + {SPREADSHEET_VALUE_TYPE_INSTANCES, "INSTANCES", ICON_NONE, "Instances", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + srna = RNA_def_struct(brna, "SpreadsheetColumn", NULL); + RNA_def_struct_sdna(srna, "SpreadsheetColumn"); + RNA_def_struct_ui_text( + srna, "Spreadsheet Column", "Persistent data associated with a spreadsheet column"); + + prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "data_type"); + RNA_def_property_enum_items(prop, data_type_items); + RNA_def_property_ui_text( + prop, "Data Type", "The data type of the corresponding column visible in the spreadsheet"); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); + + rna_def_spreadsheet_column_id(brna); + + prop = RNA_def_property(srna, "id", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "SpreadsheetColumnID"); + RNA_def_property_ui_text( + prop, "ID", "Data used to identify the corresponding data from the data source"); +} + +static void rna_def_spreadsheet_row_filter(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + static const EnumPropertyItem rule_operation_items[] = { + {SPREADSHEET_ROW_FILTER_EQUAL, "EQUAL", ICON_NONE, "Equal To", ""}, + {SPREADSHEET_ROW_FILTER_GREATER, "GREATER", ICON_NONE, "Greater Than", ""}, + {SPREADSHEET_ROW_FILTER_LESS, "LESS", ICON_NONE, "Less Than", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + srna = RNA_def_struct(brna, "SpreadsheetRowFilter", NULL); + RNA_def_struct_sdna(srna, "SpreadsheetRowFilter"); + RNA_def_struct_ui_text(srna, "Spreadsheet Row Filter", ""); + + prop = RNA_def_property(srna, "enabled", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SPREADSHEET_ROW_FILTER_ENABLED); + RNA_def_property_ui_text(prop, "Enabled", ""); + RNA_def_property_ui_icon(prop, ICON_CHECKBOX_DEHLT, 1); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); + + prop = RNA_def_property(srna, "show_expanded", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SPREADSHEET_ROW_FILTER_UI_EXPAND); + RNA_def_property_ui_text(prop, "Show Expanded", ""); + RNA_def_property_ui_icon(prop, ICON_DISCLOSURE_TRI_RIGHT, 1); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); + + prop = RNA_def_property(srna, "column_name", PROP_STRING, PROP_NONE); + RNA_def_property_ui_text(prop, "Column Name", ""); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); + + prop = RNA_def_property(srna, "operation", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rule_operation_items); + RNA_def_property_ui_text(prop, "Operation", ""); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); + + prop = RNA_def_property(srna, "value_float", PROP_FLOAT, PROP_NONE); + RNA_def_property_ui_text(prop, "Float Value", ""); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); + + prop = RNA_def_property(srna, "value_float2", PROP_FLOAT, PROP_NONE); + RNA_def_property_array(prop, 2); + RNA_def_property_ui_text(prop, "2D Vector Value", ""); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); + + prop = RNA_def_property(srna, "value_float3", PROP_FLOAT, PROP_NONE); + RNA_def_property_array(prop, 3); + RNA_def_property_ui_text(prop, "Vector Value", ""); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); + + prop = RNA_def_property(srna, "value_color", PROP_FLOAT, PROP_NONE); + RNA_def_property_array(prop, 4); + RNA_def_property_ui_text(prop, "Color Value", ""); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); + + prop = RNA_def_property(srna, "value_string", PROP_STRING, PROP_NONE); + RNA_def_property_ui_text(prop, "Text Value", ""); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); + + prop = RNA_def_property(srna, "threshold", PROP_FLOAT, PROP_NONE); + RNA_def_property_ui_text(prop, "Threshold", "How close float values need to be to be equal"); + RNA_def_property_range(prop, 0.0, FLT_MAX); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); + + prop = RNA_def_property(srna, "value_int", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "value_int"); + RNA_def_property_ui_text(prop, "Integer Value", ""); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); + + prop = RNA_def_property(srna, "value_boolean", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SPREADSHEET_ROW_FILTER_BOOL_VALUE); + RNA_def_property_ui_text(prop, "Boolean Value", ""); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); +} + static const EnumPropertyItem spreadsheet_context_type_items[] = { {SPREADSHEET_CONTEXT_OBJECT, "OBJECT", ICON_NONE, "Object", ""}, {SPREADSHEET_CONTEXT_MODIFIER, "MODIFIER", ICON_NONE, "Modifier", ""}, @@ -7545,13 +7670,18 @@ static void rna_def_space_spreadsheet(BlenderRNA *brna) srna = RNA_def_struct(brna, "SpaceSpreadsheet", "Space"); RNA_def_struct_ui_text(srna, "Space Spreadsheet", "Spreadsheet space data"); - rna_def_space_generic_show_region_toggles(srna, (1 << RGN_TYPE_FOOTER)); + rna_def_space_generic_show_region_toggles(srna, (1 << RGN_TYPE_UI) | (1 << RGN_TYPE_FOOTER)); prop = RNA_def_property(srna, "is_pinned", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", SPREADSHEET_FLAG_PINNED); RNA_def_property_ui_text(prop, "Is Pinned", "Context path is pinned"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); + prop = RNA_def_property(srna, "use_filter", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "filter_flag", SPREADSHEET_FILTER_ENABLE); + RNA_def_property_ui_text(prop, "Use Filter", ""); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); + prop = RNA_def_property(srna, "display_context_path_collapsed", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", SPREADSHEET_FLAG_CONTEXT_PATH_COLLAPSED); RNA_def_property_ui_text(prop, "Display Context Path Collapsed", ""); @@ -7566,6 +7696,7 @@ static void rna_def_space_spreadsheet(BlenderRNA *brna) RNA_def_property_boolean_sdna(prop, NULL, "filter_flag", SPREADSHEET_FILTER_SELECTED_ONLY); RNA_def_property_ui_text( prop, "Show Only Selected", "Only include rows that correspond to selected elements"); + RNA_def_property_ui_icon(prop, ICON_RESTRICT_SELECT_OFF, 0); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); prop = RNA_def_property(srna, "geometry_component_type", PROP_ENUM, PROP_NONE); @@ -7587,6 +7718,22 @@ static void rna_def_space_spreadsheet(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Object Evaluation State", ""); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); + rna_def_spreadsheet_column(brna); + + prop = RNA_def_property(srna, "columns", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, NULL, "columns", NULL); + RNA_def_property_struct_type(prop, "SpreadsheetColumn"); + RNA_def_property_ui_text(prop, "Columns", "Persistent data associated with spreadsheet columns"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); + + rna_def_spreadsheet_row_filter(brna); + + prop = RNA_def_property(srna, "row_filters", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, NULL, "row_filters", NULL); + RNA_def_property_struct_type(prop, "SpreadsheetRowFilter"); + RNA_def_property_ui_text(prop, "Row Filters", "Filters to remove rows from the displayed data"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); + func = RNA_def_function( srna, "set_geometry_node_context", "rna_spreadsheet_set_geometry_node_context"); RNA_def_function_ui_description( |