diff --git a/UI/properties-view.cpp b/UI/properties-view.cpp index acbd08b1c..5a1f0b96f 100644 --- a/UI/properties-view.cpp +++ b/UI/properties-view.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "double-slider.hpp" #include "slider-ignorewheel.hpp" #include "spinBox-ignorewheel.hpp" @@ -1332,6 +1333,44 @@ void OBSPropertiesView::AddFrameRate(obs_property_t *prop, bool &warning, }); } +void OBSPropertiesView::AddGroup(obs_property_t *prop, QFormLayout *layout) +{ + const char *name = obs_property_name(prop); + bool val = obs_data_get_bool(settings, name); + const char *desc = obs_property_description(prop); + enum obs_group_type type = obs_property_group_type(prop); + + // Create GroupBox + QGroupBox *groupBox = new QGroupBox(QT_UTF8(desc)); + groupBox->setCheckable(type == OBS_GROUP_CHECKABLE); + groupBox->setChecked(groupBox->isCheckable() ? val : true); + groupBox->setAccessibleName("group"); + groupBox->setEnabled(obs_property_enabled(prop)); + + // Create Layout and build content + QFormLayout *subLayout = new QFormLayout(); + subLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + groupBox->setLayout(subLayout); + + obs_properties_t *content = obs_property_group_content(prop); + obs_property_t *el = obs_properties_first(content); + while (el != nullptr) { + AddProperty(el, subLayout); + obs_property_next(&el); + } + + // Insert into UI + layout->setWidget(layout->rowCount(), + QFormLayout::ItemRole::SpanningRole, groupBox); + + // Register Group Widget + WidgetInfo *info = new WidgetInfo(this, prop, groupBox); + children.emplace_back(info); + + // Signals + connect(groupBox, SIGNAL(toggled()), info, SLOT(ControlChanged())); +} + void OBSPropertiesView::AddProperty(obs_property_t *property, QFormLayout *layout) { @@ -1381,6 +1420,8 @@ void OBSPropertiesView::AddProperty(obs_property_t *property, case OBS_PROPERTY_FRAME_RATE: AddFrameRate(property, warning, layout, label); break; + case OBS_PROPERTY_GROUP: + AddGroup(property, layout); } if (widget && !obs_property_enabled(property)) @@ -1388,7 +1429,8 @@ void OBSPropertiesView::AddProperty(obs_property_t *property, if (!label && type != OBS_PROPERTY_BOOL && - type != OBS_PROPERTY_BUTTON) + type != OBS_PROPERTY_BUTTON && + type != OBS_PROPERTY_GROUP) label = new QLabel(QT_UTF8(obs_property_description(property))); if (warning && label) //TODO: select color based on background color @@ -1707,6 +1749,13 @@ bool WidgetInfo::FontChanged(const char *setting) return true; } +void WidgetInfo::GroupChanged(const char *setting) +{ + QGroupBox *groupbox = static_cast(widget); + obs_data_set_bool(view->settings, setting, + groupbox->isCheckable() ? groupbox->isChecked() : true); +} + void WidgetInfo::EditableListChanged() { const char *setting = obs_property_name(property); @@ -1776,6 +1825,7 @@ void WidgetInfo::ControlChanged() if (!FrameRateChanged(widget, setting, view->settings)) return; break; + case OBS_PROPERTY_GROUP: GroupChanged(setting); return; } if (view->callback && !view->deferUpdate) diff --git a/UI/properties-view.hpp b/UI/properties-view.hpp index 4a153c745..0bda9ec08 100644 --- a/UI/properties-view.hpp +++ b/UI/properties-view.hpp @@ -33,6 +33,7 @@ private: void ListChanged(const char *setting); bool ColorChanged(const char *setting); bool FontChanged(const char *setting); + void GroupChanged(const char *setting); void EditableListChanged(); void ButtonClicked(); @@ -103,6 +104,8 @@ private: void AddFrameRate(obs_property_t *prop, bool &warning, QFormLayout *layout, QLabel *&label); + void AddGroup(obs_property_t *prop, QFormLayout *layout); + void AddProperty(obs_property_t *property, QFormLayout *layout); void resizeEvent(QResizeEvent *event) override; diff --git a/libobs/obs-properties.c b/libobs/obs-properties.c index 6beb5a9ab..ad62d5b4a 100644 --- a/libobs/obs-properties.c +++ b/libobs/obs-properties.c @@ -86,6 +86,11 @@ struct frame_rate_data { DARRAY(struct frame_rate_range) ranges; }; +struct group_data { + enum obs_group_type type; + obs_properties_t *content; +}; + static inline void path_data_free(struct path_data *data) { bfree(data->default_path); @@ -140,6 +145,10 @@ static inline void frame_rate_data_free(struct frame_rate_data *data) da_free(data->ranges); } +static inline void group_data_free(struct group_data *data) { + obs_properties_destroy(data->content); +} + struct obs_properties; struct obs_property { @@ -166,6 +175,7 @@ struct obs_properties { struct obs_property *first_property; struct obs_property **last; + struct obs_property *parent; }; obs_properties_t *obs_properties_create(void) @@ -223,6 +233,8 @@ static void obs_property_destroy(struct obs_property *property) editable_list_data_free(get_property_data(property)); else if (property->type == OBS_PROPERTY_FRAME_RATE) frame_rate_data_free(get_property_data(property)); + else if (property->type == OBS_PROPERTY_GROUP) + group_data_free(get_property_data(property)); bfree(property->name); bfree(property->desc); @@ -265,12 +277,26 @@ obs_property_t *obs_properties_get(obs_properties_t *props, const char *name) if (strcmp(property->name, name) == 0) return property; + if (property->type == OBS_PROPERTY_GROUP) { + obs_properties_t *group = + obs_property_group_content(property); + obs_property_t *found = obs_properties_get(group, name); + if (found != NULL) { + return found; + } + } + property = property->next; } return NULL; } +obs_properties_t *obs_properties_get_parent(obs_properties_t *props) +{ + return props->parent ? props->parent->parent : NULL; +} + void obs_properties_remove_by_name(obs_properties_t *props, const char *name) { if (!props) @@ -338,6 +364,7 @@ static inline size_t get_property_size(enum obs_property_type type) case OBS_PROPERTY_EDITABLE_LIST: return sizeof(struct editable_list_data); case OBS_PROPERTY_FRAME_RATE:return sizeof(struct frame_rate_data); + case OBS_PROPERTY_GROUP: return sizeof(struct group_data); } return 0; @@ -362,7 +389,18 @@ static inline struct obs_property *new_prop(struct obs_properties *props, return p; } -static inline bool has_prop(struct obs_properties *props, const char *name) +static inline obs_properties_t *get_topmost_parent(obs_properties_t *props) +{ + obs_properties_t *parent = props; + obs_properties_t *last_parent = parent; + while (parent) { + last_parent = parent; + parent = obs_properties_get_parent(parent); + } + return last_parent; +} + +static inline bool contains_prop(struct obs_properties *props, const char *name) { struct obs_property *p = props->first_property; @@ -372,12 +410,23 @@ static inline bool has_prop(struct obs_properties *props, const char *name) return true; } + if (p->type == OBS_PROPERTY_GROUP) { + if (contains_prop(obs_property_group_content(p), name)) { + return true; + } + } + p = p->next; } return false; } +static inline bool has_prop(struct obs_properties *props, const char *name) +{ + return contains_prop(get_topmost_parent(props), name); +} + static inline void *get_property_data(struct obs_property *prop) { return (uint8_t*)prop + sizeof(struct obs_property); @@ -578,6 +627,70 @@ obs_property_t *obs_properties_add_frame_rate(obs_properties_t *props, return p; } +static bool check_property_group_recursion(obs_properties_t *parent, + obs_properties_t *group) +{ + /* Scan the group for the parent. */ + obs_property_t *current_property = group->first_property; + while (current_property) { + if (current_property->type == OBS_PROPERTY_GROUP) { + obs_properties_t *cprops = + obs_property_group_content(current_property); + if (cprops == parent) { + /* Contains find_props */ + return true; + } else if (cprops == group) { + /* Contains self, shouldn't be possible but + * lets verify anyway. */ + return true; + } + check_property_group_recursion(cprops, group); + } + + current_property = current_property->next; + } + + return false; +} + +static bool check_property_group_duplicates(obs_properties_t *parent, + obs_properties_t *group) +{ + obs_property_t *current_property = group->first_property; + while (current_property) { + if (has_prop(parent, current_property->name)) { + return true; + } + + current_property = current_property->next; + } + + return false; +} + +obs_property_t *obs_properties_add_group(obs_properties_t *props, + const char *name, const char *desc, enum obs_group_type type, + obs_properties_t *group) +{ + if (!props || has_prop(props, name)) return NULL; + if (!group) return NULL; + + /* Prevent recursion. */ + if (props == group) return NULL; + if (check_property_group_recursion(props, group)) return NULL; + + /* Prevent duplicate properties */ + if (check_property_group_duplicates(props, group)) return NULL; + + obs_property_t *p = new_prop(props, name, desc, OBS_PROPERTY_GROUP); + group->parent = p; + + struct group_data *data = get_property_data(p); + data->type = type; + data->content = group; + return p; +} + /* ------------------------------------------------------------------------- */ static inline bool is_combo(struct obs_property *p) @@ -630,9 +743,11 @@ bool obs_property_modified(obs_property_t *p, obs_data_t *settings) { if (p) { if (p->modified) { - return p->modified(p->parent, p, settings); + obs_properties_t *top = get_topmost_parent(p->parent); + return p->modified(top, p, settings); } else if (p->modified2) { - return p->modified2(p->priv, p->parent, p, settings); + obs_properties_t *top = get_topmost_parent(p->parent); + return p->modified2(p->priv, top, p, settings); } } return false; @@ -645,9 +760,10 @@ bool obs_property_button_clicked(obs_property_t *p, void *obj) struct button_data *data = get_type_data(p, OBS_PROPERTY_BUTTON); if (data && data->callback) { + obs_properties_t *top = get_topmost_parent(p->parent); if (p->priv) - return data->callback(p->parent, p, p->priv); - return data->callback(p->parent, p, + return data->callback(top, p, p->priv); + return data->callback(top, p, (context ? context->data : NULL)); } } @@ -1137,3 +1253,15 @@ enum obs_text_type obs_proprety_text_type(obs_property_t *p) { return obs_property_text_type(p); } + +enum obs_group_type obs_property_group_type(obs_property_t *p) +{ + struct group_data *data = get_type_data(p, OBS_PROPERTY_GROUP); + return data ? data->type : OBS_COMBO_INVALID; +} + +obs_properties_t *obs_property_group_content(obs_property_t *p) +{ + struct group_data *data = get_type_data(p, OBS_PROPERTY_GROUP); + return data ? data->content : NULL; +} diff --git a/libobs/obs-properties.h b/libobs/obs-properties.h index ad1c2ee98..95db304fc 100644 --- a/libobs/obs-properties.h +++ b/libobs/obs-properties.h @@ -55,6 +55,7 @@ enum obs_property_type { OBS_PROPERTY_FONT, OBS_PROPERTY_EDITABLE_LIST, OBS_PROPERTY_FRAME_RATE, + OBS_PROPERTY_GROUP, }; enum obs_combo_format { @@ -93,6 +94,12 @@ enum obs_number_type { OBS_NUMBER_SLIDER }; +enum obs_group_type { + OBS_COMBO_INVALID, + OBS_GROUP_NORMAL, + OBS_GROUP_CHECKABLE, +}; + #define OBS_FONT_BOLD (1<<0) #define OBS_FONT_ITALIC (1<<1) #define OBS_FONT_UNDERLINE (1<<2) @@ -122,6 +129,8 @@ EXPORT obs_property_t *obs_properties_first(obs_properties_t *props); EXPORT obs_property_t *obs_properties_get(obs_properties_t *props, const char *property); +EXPORT obs_properties_t *obs_properties_get_parent(obs_properties_t *props); + /** Remove a property from a properties list. * * Removes a property from a properties list. Only valid in either @@ -231,6 +240,11 @@ EXPORT obs_property_t *obs_properties_add_editable_list(obs_properties_t *props, EXPORT obs_property_t *obs_properties_add_frame_rate(obs_properties_t *props, const char *name, const char *description); +EXPORT obs_property_t *obs_properties_add_group(obs_properties_t *props, + const char *name, const char *description, enum obs_group_type type, + obs_properties_t *group); + + /* ------------------------------------------------------------------------- */ /** @@ -349,6 +363,9 @@ EXPORT struct media_frames_per_second obs_property_frame_rate_fps_range_min( EXPORT struct media_frames_per_second obs_property_frame_rate_fps_range_max( obs_property_t *p, size_t idx); +EXPORT enum obs_group_type obs_property_group_type(obs_property_t *p); +EXPORT obs_properties_t *obs_property_group_content(obs_property_t *p); + #ifndef SWIG DEPRECATED EXPORT enum obs_text_type obs_proprety_text_type(obs_property_t *p);