| 3069 ct->add(ct, scroll_area, &layout); |
3073 ct->add(ct, scroll_area, &layout); |
| 3070 |
3074 |
| 3071 UiListBox2 *uilistbox = malloc(sizeof(UiListBox2)); |
3075 UiListBox2 *uilistbox = malloc(sizeof(UiListBox2)); |
| 3072 uilistbox->obj = obj; |
3076 uilistbox->obj = obj; |
| 3073 uilistbox->listbox = GTK_LIST_BOX(listbox); |
3077 uilistbox->listbox = GTK_LIST_BOX(listbox); |
| 3074 uilistbox->rows = cxArrayListCreate(NULL, CX_STORE_POINTERS, 64); |
3078 uilistbox->rows = cxArrayListCreate(NULL, sizeof(UiListBox2Row), 32); |
| |
3079 cxSetDestructor(uilistbox->rows, ui_destroy_listbox2_row); |
| 3075 uilistbox->getvalue = args->getvalue; |
3080 uilistbox->getvalue = args->getvalue; |
| 3076 uilistbox->getvaluedata = args->getvaluedata; |
3081 uilistbox->getvaluedata = args->getvaluedata; |
| 3077 uilistbox->getdepth = args->getdepth; |
|
| 3078 uilistbox->getdepthdata = args->getdepthdata; |
|
| 3079 uilistbox->onactivate = args->onactivate; |
3082 uilistbox->onactivate = args->onactivate; |
| 3080 uilistbox->onactivatedata = args->onactivatedata; |
3083 uilistbox->onactivatedata = args->onactivatedata; |
| 3081 uilistbox->onactivate_action = args->onactivate_action ? strdup(args->onactivate_action) : NULL; |
3084 uilistbox->onactivate_action = args->onactivate_action ? strdup(args->onactivate_action) : NULL; |
| 3082 uilistbox->onbuttonclick = args->onbuttonclick; |
3085 uilistbox->onbuttonclick = args->onbuttonclick; |
| 3083 uilistbox->onbuttonclickdata = args->onbuttonclickdata; |
3086 uilistbox->onbuttonclickdata = args->onbuttonclickdata; |
| 3109 listbox, |
3112 listbox, |
| 3110 "destroy", |
3113 "destroy", |
| 3111 G_CALLBACK(ui_destroy_sourcelist2), |
3114 G_CALLBACK(ui_destroy_sourcelist2), |
| 3112 uilistbox); |
3115 uilistbox); |
| 3113 |
3116 |
| 3114 if(args->onactivate) { |
3117 if(args->onactivate || args->onactivate_action) { |
| 3115 g_signal_connect( |
3118 g_signal_connect( |
| 3116 listbox, |
3119 listbox, |
| 3117 "row-activated", |
3120 "row-activated", |
| 3118 G_CALLBACK(ui_listbox2_row_activate), |
3121 G_CALLBACK(ui_listbox2_row_activate), |
| 3119 NULL); |
3122 NULL); |
| 3120 } |
3123 } |
| 3121 |
3124 |
| 3122 return scroll_area; |
3125 return scroll_area; |
| 3123 } |
3126 } |
| 3124 |
3127 |
| 3125 |
3128 static void listbox2_button_clicked(GtkWidget *button, UiListBox2Row *data) { |
| 3126 static int listbox2_update_item(UiListBox2 *listbox, GtkWidget *row, UiList *list, void *elm, int index) { |
3129 UiEvent event; |
| 3127 UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL }; |
3130 event.obj = data->listbox->obj; |
| 3128 if(listbox->getvalue) { |
3131 event.window = event.obj->window; |
| 3129 listbox->getvalue(list,elm, index, &item, listbox->getvaluedata); |
3132 event.document = event.obj->ctx->document; |
| 3130 } |
3133 event.eventdata = NULL; |
| 3131 |
3134 event.eventdatatype = UI_EVENT_DATA_NULL; |
| 3132 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); |
3135 event.intval = data->index; |
| 3133 |
3136 event.set = ui_get_setop(); |
| 3134 return 0; |
3137 |
| |
3138 if(data->listbox->onbuttonclick) { |
| |
3139 data->listbox->onbuttonclick(&event, data->listbox->onbuttonclickdata); |
| |
3140 } |
| |
3141 |
| |
3142 if(data->listbox->onbuttonclick_action) { |
| |
3143 uic_action_callback(&event, data->listbox->onbuttonclick_action); |
| |
3144 } |
| |
3145 } |
| |
3146 |
| |
3147 |
| |
3148 static void listbox2_node_children_update_visibility(UiListBox2Row *rowdata, int visible) { |
| |
3149 CxIterator i = cxListIterator(rowdata->children); |
| |
3150 int child_visible = visible ? rowdata->expanded : 0; |
| |
3151 cx_foreach(UiListBox2Row *, child, i) { |
| |
3152 gtk_widget_set_visible(child->row, child_visible); |
| |
3153 listbox2_node_children_update_visibility(child, child_visible); |
| |
3154 } |
| |
3155 } |
| |
3156 |
| |
3157 #if GTK_CHECK_VERSION(4, 0, 0) |
| |
3158 |
| |
3159 void listbox2_node_expand( |
| |
3160 GtkGestureClick *self, |
| |
3161 gint n_press, |
| |
3162 gdouble x, |
| |
3163 gdouble y, |
| |
3164 UiListBox2Row *rowdata) |
| |
3165 { |
| |
3166 rowdata->expanded = !rowdata->expanded; |
| |
3167 if(rowdata->expanded) { |
| |
3168 gtk_image_set_from_icon_name(GTK_IMAGE(rowdata->expander), "pan-down-symbolic"); |
| |
3169 } else { |
| |
3170 gtk_image_set_from_icon_name(GTK_IMAGE(rowdata->expander), "pan-end-symbolic"); |
| |
3171 } |
| |
3172 listbox2_node_children_update_visibility(rowdata, rowdata->expanded); |
| |
3173 } |
| |
3174 |
| |
3175 #endif |
| |
3176 #if GTK_MAJOR_VERSION == 3 |
| |
3177 |
| |
3178 #endif |
| |
3179 |
| |
3180 #define LISTBOX2_ITEM_HBOX_SPACING 10 |
| |
3181 static void listbox2_update_item(UiListBox2Row *rowdata, GtkWidget *row, UiSourceListItem *item, int identation) { |
| |
3182 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, LISTBOX2_ITEM_HBOX_SPACING); |
| |
3183 |
| |
3184 rowdata->expanded = 1; |
| |
3185 GtkWidget *expander = gtk_image_new_from_icon_name("pan-down-symbolic"); |
| |
3186 int exp_width = 0; |
| |
3187 #if GTK_CHECK_VERSION(4, 0, 0) |
| |
3188 gtk_widget_measure(expander, |
| |
3189 GTK_ORIENTATION_HORIZONTAL, |
| |
3190 -1, |
| |
3191 &exp_width, |
| |
3192 NULL, |
| |
3193 NULL, |
| |
3194 NULL); |
| |
3195 |
| |
3196 GtkGesture *click = gtk_gesture_click_new(); |
| |
3197 gtk_widget_add_controller(expander, GTK_EVENT_CONTROLLER(click)); |
| |
3198 g_signal_connect(click, "released", G_CALLBACK(listbox2_node_expand), rowdata); |
| |
3199 #else |
| |
3200 int exp_height; |
| |
3201 gtk_widget_get_preferred_width(expander, &exp_width, &exp_height); |
| |
3202 #endif |
| |
3203 gtk_widget_set_margin_start(hbox, identation * (exp_width+LISTBOX2_ITEM_HBOX_SPACING)); |
| |
3204 |
| |
3205 WIDGET_NO_SHOW_ALL(expander); |
| |
3206 gtk_widget_set_visible(expander, FALSE); |
| |
3207 // normal items have the expander icon in the front |
| |
3208 if(!item->is_header) { |
| |
3209 BOX_ADD(hbox, expander); |
| |
3210 } |
| |
3211 rowdata->expander = expander; |
| |
3212 |
| |
3213 if(item->icon) { |
| |
3214 GtkWidget *icon = ICON_IMAGE(item->icon); |
| |
3215 BOX_ADD(hbox, icon); |
| |
3216 } |
| |
3217 GtkWidget *label = gtk_label_new(item->label); |
| |
3218 gtk_widget_set_halign(label, GTK_ALIGN_START); |
| |
3219 |
| |
3220 BOX_ADD_EXPAND(hbox, label); |
| |
3221 |
| |
3222 // button |
| |
3223 if(item->button_label || item->button_icon) { |
| |
3224 GtkWidget *button = gtk_button_new(); |
| |
3225 gtk_button_set_label(GTK_BUTTON(button), item->button_label); |
| |
3226 ui_button_set_icon_name(button, item->button_icon); |
| |
3227 WIDGET_ADD_CSS_CLASS(button, "flat"); |
| |
3228 BOX_ADD(hbox, button); |
| |
3229 g_signal_connect( |
| |
3230 button, |
| |
3231 "clicked", |
| |
3232 G_CALLBACK(listbox2_button_clicked), |
| |
3233 rowdata); |
| |
3234 gtk_widget_set_visible(button, FALSE); |
| |
3235 WIDGET_NO_SHOW_ALL(button); |
| |
3236 } |
| |
3237 |
| |
3238 // headings have the expander at the end |
| |
3239 if(item->is_header) { |
| |
3240 BOX_ADD(hbox, expander); |
| |
3241 WIDGET_ADD_CSS_CLASS(label, "ui-listbox-header-row"); |
| |
3242 rowdata->heading = TRUE; |
| |
3243 } |
| |
3244 |
| |
3245 LISTBOX_ROW_SET_CHILD(row, hbox); |
| |
3246 g_object_set_data(G_OBJECT(row), "ui-listbox-row-data", rowdata); |
| 3135 } |
3247 } |
| 3136 |
3248 |
| 3137 static void listbox2_update_all(UiListBox2 *listbox) { |
3249 static void listbox2_update_all(UiListBox2 *listbox) { |
| 3138 gtk_list_box_remove_all(listbox->listbox); |
3250 gtk_list_box_remove_all(listbox->listbox); |
| 3139 cxListClear(listbox->rows); |
3251 cxListClear(listbox->rows); |
| 3143 UiList *list = listbox->var->value; |
3255 UiList *list = listbox->var->value; |
| 3144 if(!list) { |
3256 if(!list) { |
| 3145 return; |
3257 return; |
| 3146 } |
3258 } |
| 3147 |
3259 |
| |
3260 UiListBox2Row **stack; |
| |
3261 size_t stack_size = 16; |
| |
3262 stack = calloc(stack_size, sizeof(UiListBox2Row*)); |
| 3148 |
3263 |
| 3149 int index = 0; |
3264 int index = 0; |
| |
3265 UiBool heading = FALSE; |
| 3150 void *elm = list->first(list); |
3266 void *elm = list->first(list); |
| 3151 while(elm) { |
3267 while(elm) { |
| |
3268 UiSourceListItem item = { 0 }; |
| |
3269 if(listbox->getvalue) { |
| |
3270 listbox->getvalue(list, elm, index, &item, listbox->getvaluedata); |
| |
3271 } |
| |
3272 |
| 3152 GtkWidget *row = gtk_list_box_row_new(); |
3273 GtkWidget *row = gtk_list_box_row_new(); |
| 3153 cxListAdd(listbox->rows, row); |
|
| 3154 gtk_list_box_append(listbox->listbox, row); |
3274 gtk_list_box_append(listbox->listbox, row); |
| 3155 int node_depth = listbox2_update_item(listbox, row, list, elm, index); |
3275 UiListBox2Row row_data = {0}; |
| |
3276 row_data.listbox = listbox; |
| |
3277 row_data.row = row; |
| |
3278 row_data.index = index; |
| |
3279 row_data.depth = item.depth; |
| |
3280 cxListAdd(listbox->rows, &row_data); |
| |
3281 UiListBox2Row *row_data_ptr = cxListLast(listbox->rows); |
| |
3282 listbox2_update_item(row_data_ptr, row, &item, item.depth - (item.depth > 0 ? heading : 0)); |
| |
3283 |
| |
3284 if(item.depth >= stack_size) { |
| |
3285 stack_size += 8; |
| |
3286 stack = realloc(stack, stack_size * sizeof(UiListBox2Row*)); |
| |
3287 } |
| |
3288 stack[item.depth] = row_data_ptr; |
| |
3289 |
| |
3290 if(item.depth > 0) { |
| |
3291 // add row to parent |
| |
3292 UiListBox2Row *parent = item.depth-1 < stack_size ? stack[item.depth-1] : NULL; |
| |
3293 if(parent) { |
| |
3294 if(!parent->children) { |
| |
3295 parent->children = cxArrayListCreate(NULL, CX_STORE_POINTERS, 8); |
| |
3296 gtk_widget_set_visible(parent->expander, TRUE); |
| |
3297 WIDGET_ENABLE_SHOW_ALL(parent->expander); |
| |
3298 } |
| |
3299 cxListAdd(parent->children, row_data_ptr); |
| |
3300 } else { |
| |
3301 fprintf(stderr, "Error: sourcelist2: illegal depth: no parent found\n"); |
| |
3302 } |
| |
3303 } else { |
| |
3304 heading = item.is_header != 0; |
| |
3305 } |
| |
3306 |
| |
3307 free(item.label); |
| |
3308 free(item.icon); |
| |
3309 free(item.badge); |
| |
3310 free(item.button_label); |
| |
3311 free(item.button_icon); |
| 3156 |
3312 |
| 3157 elm = list->next(list); |
3313 elm = list->next(list); |
| 3158 index++; |
3314 index++; |
| 3159 } |
3315 } |
| |
3316 |
| |
3317 cxListShrink(listbox->rows); |
| |
3318 free(stack); |
| 3160 } |
3319 } |
| 3161 |
3320 |
| 3162 void ui_listbox2_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data) { |
3321 void ui_listbox2_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data) { |
| 3163 |
3322 UiListBox2Row *rowdata = g_object_get_data(G_OBJECT(row), "ui-listbox-row-data"); |
| |
3323 if(!rowdata) { |
| |
3324 return; |
| |
3325 } |
| |
3326 UiListBox2 *listbox = rowdata->listbox; |
| |
3327 UiVar *var = listbox->var; |
| |
3328 if(!var) { |
| |
3329 return; // shouldn't happen |
| |
3330 } |
| |
3331 |
| |
3332 UiList *list = var->value; |
| |
3333 void *elm = list->get(list, rowdata->index); |
| |
3334 |
| |
3335 UiEvent event; |
| |
3336 event.obj = listbox->obj; |
| |
3337 event.window = event.obj->window; |
| |
3338 event.document = event.obj->ctx->document; |
| |
3339 event.eventdatatype = UI_EVENT_DATA_LIST_ELM; |
| |
3340 event.eventdata = elm; |
| |
3341 event.intval = rowdata->index; |
| |
3342 event.set = ui_get_setop(); |
| |
3343 |
| |
3344 if(listbox->onactivate) { |
| |
3345 listbox->onactivate(&event, listbox->onactivatedata); |
| |
3346 } |
| |
3347 if(listbox->onactivate_action) { |
| |
3348 uic_action_callback(&event, listbox->onactivate_action); |
| |
3349 } |
| 3164 } |
3350 } |
| 3165 |
3351 |
| 3166 |
3352 |
| 3167 void ui_listbox2_update(UiList *list, int i) { |
3353 void ui_listbox2_update(UiList *list, int i) { |
| 3168 UiListBox2 *listbox = list->obj; |
3354 UiListBox2 *listbox = list->obj; |
| 3169 if(i >= 0) { |
3355 if(i >= 0) { |
| 3170 // TODO |
3356 // TODO: update single row |
| |
3357 listbox2_update_all(listbox); |
| 3171 } else { |
3358 } else { |
| 3172 listbox2_update_all(listbox); |
3359 listbox2_update_all(listbox); |
| 3173 } |
3360 } |
| 3174 } |
3361 } |
| 3175 |
3362 |