| 93 tableview->selection.count = 0; |
93 tableview->selection.count = 0; |
| 94 tableview->selection.rows = NULL; |
94 tableview->selection.rows = NULL; |
| 95 tableview->current_row = -1; |
95 tableview->current_row = -1; |
| 96 tableview->getstyle = args->getstyle; |
96 tableview->getstyle = args->getstyle; |
| 97 tableview->getstyledata = args->getstyledata; |
97 tableview->getstyledata = args->getstyledata; |
| 98 #if GTK_CHECK_VERSION(4, 10, 0) |
98 tableview->onsave = args->onsave; |
| 99 tableview->default_attributes = pango_attr_list_new(); |
99 tableview->onsavedata = args->onsavedata; |
| 100 #endif |
|
| 101 |
100 |
| 102 if(args->getvalue2) { |
101 if(args->getvalue2) { |
| 103 tableview->getvalue = args->getvalue2; |
102 tableview->getvalue = args->getvalue2; |
| 104 tableview->getvaluedata = args->getvalue2data; |
103 tableview->getvaluedata = args->getvalue2data; |
| 105 } else if(args->getvalue) { |
104 } else if(args->getvalue) { |
| 143 obj->i = i; |
142 obj->i = i; |
| 144 return obj; |
143 return obj; |
| 145 } |
144 } |
| 146 |
145 |
| 147 /* END GObject wrapper for generic pointers */ |
146 /* END GObject wrapper for generic pointers */ |
| |
147 |
| |
148 typedef struct UiCellEntry { |
| |
149 GtkEntry *entry; |
| |
150 UiListView *listview; |
| |
151 char *previous_value; |
| |
152 int row; |
| |
153 int col; |
| |
154 } UiCellEntry; |
| |
155 |
| |
156 static void cell_save_value(UiCellEntry *data, int restore) { |
| |
157 if(data->listview && data->listview->onsave) { |
| |
158 UiVar *var = data->listview->var; |
| |
159 UiList *list = var ? var->value : NULL; |
| |
160 const char *str = ENTRY_GET_TEXT(data->entry); |
| |
161 UiCellValue value; |
| |
162 value.string = str; |
| |
163 value.type = UI_STRING_EDITABLE; |
| |
164 if(data->listview->onsave(list, data->row, data->col, &value, data->listview->onsavedata)) { |
| |
165 free(data->previous_value); |
| |
166 data->previous_value = strdup(str); |
| |
167 } else if(restore) { |
| |
168 ENTRY_SET_TEXT(data->entry, data->previous_value); |
| |
169 } |
| |
170 } |
| |
171 } |
| |
172 |
| |
173 static void cell_entry_leave_focus( |
| |
174 GtkEventControllerFocus *self, |
| |
175 UiCellEntry *data) |
| |
176 { |
| |
177 // TODO: use a different singal to track focus |
| |
178 // we only want to call cell_save_value, when another entry is selected, |
| |
179 // not when the window loses focus or something like that |
| |
180 cell_save_value(data, TRUE); |
| |
181 } |
| |
182 |
| |
183 static void cell_entry_destroy(GtkWidget *object, UiCellEntry *data) { |
| |
184 free(data->previous_value); |
| |
185 free(data); |
| |
186 } |
| |
187 |
| |
188 static void cell_entry_unmap(GtkWidget *w, UiCellEntry *data) { |
| |
189 const char *text = ENTRY_GET_TEXT(w); |
| |
190 cell_save_value(data, FALSE); |
| |
191 } |
| |
192 |
| |
193 static void cell_entry_activate( |
| |
194 GtkEntry *self, |
| |
195 UiCellEntry *data) |
| |
196 { |
| |
197 cell_save_value(data, TRUE); |
| |
198 } |
| 148 |
199 |
| 149 static void column_factory_setup(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) { |
200 static void column_factory_setup(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) { |
| 150 UiColData *col = userdata; |
201 UiColData *col = userdata; |
| 151 UiModel *model = col->listview->model; |
202 UiModel *model = col->listview->model; |
| 152 UiModelType type = model->types[col->model_column]; |
203 UiModelType type = model->types[col->model_column]; |
| 160 g_object_set_data(G_OBJECT(hbox), "image", image); |
211 g_object_set_data(G_OBJECT(hbox), "image", image); |
| 161 g_object_set_data(G_OBJECT(hbox), "label", label); |
212 g_object_set_data(G_OBJECT(hbox), "label", label); |
| 162 } else if(type == UI_ICON) { |
213 } else if(type == UI_ICON) { |
| 163 GtkWidget *image = gtk_image_new(); |
214 GtkWidget *image = gtk_image_new(); |
| 164 gtk_list_item_set_child(item, image); |
215 gtk_list_item_set_child(item, image); |
| |
216 } else if(type == UI_STRING_EDITABLE) { |
| |
217 GtkWidget *textfield = gtk_entry_new(); |
| |
218 gtk_widget_add_css_class(textfield, "ui-table-entry"); |
| |
219 gtk_list_item_set_child(item, textfield); |
| |
220 |
| |
221 UiCellEntry *entry_data = malloc(sizeof(UiCellEntry)); |
| |
222 entry_data->entry = GTK_ENTRY(textfield); |
| |
223 entry_data->listview = NULL; |
| |
224 entry_data->previous_value = NULL; |
| |
225 entry_data->col = 0; |
| |
226 entry_data->row = 0; |
| |
227 g_object_set_data(G_OBJECT(textfield), "ui_entry_data", entry_data); |
| |
228 |
| |
229 g_signal_connect( |
| |
230 textfield, |
| |
231 "destroy", |
| |
232 G_CALLBACK(cell_entry_destroy), |
| |
233 entry_data); |
| |
234 g_signal_connect( |
| |
235 textfield, |
| |
236 "activate", |
| |
237 G_CALLBACK(cell_entry_activate), |
| |
238 entry_data); |
| |
239 g_signal_connect( |
| |
240 textfield, |
| |
241 "unmap", |
| |
242 G_CALLBACK(cell_entry_unmap), |
| |
243 entry_data); |
| |
244 |
| |
245 GtkEventController *focus_controller = gtk_event_controller_focus_new(); |
| |
246 g_signal_connect(focus_controller, "leave", G_CALLBACK(cell_entry_leave_focus), entry_data); |
| |
247 gtk_widget_add_controller(textfield, focus_controller); |
| 165 } else { |
248 } else { |
| 166 GtkWidget *label = gtk_label_new(NULL); |
249 GtkWidget *label = gtk_label_new(NULL); |
| 167 gtk_label_set_xalign(GTK_LABEL(label), 0); |
250 gtk_label_set_xalign(GTK_LABEL(label), 0); |
| 168 gtk_list_item_set_child(item, label); |
251 gtk_list_item_set_child(item, label); |
| 169 } |
252 } |
| 294 if(freevalue) { |
377 if(freevalue) { |
| 295 free(data2); |
378 free(data2); |
| 296 } |
379 } |
| 297 break; |
380 break; |
| 298 } |
381 } |
| |
382 case UI_STRING_EDITABLE: { |
| |
383 UiCellEntry *entry = g_object_get_data(G_OBJECT(child), "ui_entry_data"); |
| |
384 if(entry) { |
| |
385 entry->listview = col->listview; |
| |
386 entry->row = obj->i; |
| |
387 entry->col = col->data_column; |
| |
388 entry->previous_value = strdup(data); |
| |
389 } |
| |
390 ENTRY_SET_TEXT(child, data); |
| |
391 break; |
| |
392 } |
| 299 } |
393 } |
| 300 |
394 |
| 301 if(attributes != listview->current_row_attributes) { |
395 if(attributes != listview->current_row_attributes) { |
| 302 pango_attr_list_unref(attributes); |
396 pango_attr_list_unref(attributes); |
| 303 } |
397 } |
| 313 row->bound--; |
407 row->bound--; |
| 314 if(row->bound == 0) { |
408 if(row->bound == 0) { |
| 315 cxMapRemove(listview->bound_rows, row_key); |
409 cxMapRemove(listview->bound_rows, row_key); |
| 316 } |
410 } |
| 317 } // else: should not happen |
411 } // else: should not happen |
| |
412 |
| |
413 GtkWidget *child = gtk_list_item_get_child(item); |
| |
414 UiCellEntry *entry = g_object_get_data(G_OBJECT(child), "ui_entry_data"); |
| |
415 if(entry) { |
| |
416 cell_save_value(entry, FALSE); |
| |
417 entry->listview = NULL; |
| |
418 free(entry->previous_value); |
| |
419 entry->previous_value = NULL; |
| |
420 } |
| 318 } |
421 } |
| 319 |
422 |
| 320 |
423 |
| 321 static GtkSelectionModel* create_selection_model(UiListView *listview, GListStore *liststore, bool multiselection) { |
424 static GtkSelectionModel* create_selection_model(UiListView *listview, GListStore *liststore, bool multiselection) { |
| 322 GtkSelectionModel *selection_model; |
425 GtkSelectionModel *selection_model; |
| 1906 } |
2009 } |
| 1907 free(v->elements); |
2010 free(v->elements); |
| 1908 } |
2011 } |
| 1909 #if GTK_CHECK_VERSION(4, 10, 0) |
2012 #if GTK_CHECK_VERSION(4, 10, 0) |
| 1910 free(v->columns); |
2013 free(v->columns); |
| 1911 pango_attr_list_unref(v->default_attributes); |
|
| 1912 pango_attr_list_unref(v->current_row_attributes); |
2014 pango_attr_list_unref(v->current_row_attributes); |
| 1913 cxMapFree(v->bound_rows); |
2015 cxMapFree(v->bound_rows); |
| 1914 #endif |
2016 #endif |
| 1915 free(v->selection.rows); |
2017 free(v->selection.rows); |
| 1916 free(v); |
2018 free(v); |
| 2180 if(data->callback2) { |
2282 if(data->callback2) { |
| 2181 data->callback2(&event, data->userdata2); |
2283 data->callback2(&event, data->userdata2); |
| 2182 } |
2284 } |
| 2183 } |
2285 } |
| 2184 |
2286 |
| 2185 static GtkWidget* create_listbox_row(UiListBox *listbox, UiListBoxSubList *sublist, UiSubListItem *item, int index) { |
2287 static void listbox_fill_row(UiListBox *listbox, GtkWidget *row, UiListBoxSubList *sublist, UiSubListItem *item, int index) { |
| 2186 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); |
2288 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); |
| 2187 if(item->icon) { |
2289 if(item->icon) { |
| 2188 GtkWidget *icon = ICON_IMAGE(item->icon); |
2290 GtkWidget *icon = ICON_IMAGE(item->icon); |
| 2189 BOX_ADD(hbox, icon); |
2291 BOX_ADD(hbox, icon); |
| 2190 } |
2292 } |
| 2192 gtk_widget_set_halign(label, GTK_ALIGN_START); |
2294 gtk_widget_set_halign(label, GTK_ALIGN_START); |
| 2193 BOX_ADD_EXPAND(hbox, label); |
2295 BOX_ADD_EXPAND(hbox, label); |
| 2194 if(item->badge) { |
2296 if(item->badge) { |
| 2195 |
2297 |
| 2196 } |
2298 } |
| 2197 GtkWidget *row = gtk_list_box_row_new(); |
|
| 2198 LISTBOX_ROW_SET_CHILD(row, hbox); |
2299 LISTBOX_ROW_SET_CHILD(row, hbox); |
| 2199 |
2300 |
| 2200 // signals |
2301 // signals |
| 2201 UiEventDataExt *event = malloc(sizeof(UiEventDataExt)); |
2302 UiEventDataExt *event = malloc(sizeof(UiEventDataExt)); |
| 2202 memset(event, 0, sizeof(UiEventDataExt)); |
2303 memset(event, 0, sizeof(UiEventDataExt)); |
| 2243 "clicked", |
2344 "clicked", |
| 2244 G_CALLBACK(listbox_button_clicked), |
2345 G_CALLBACK(listbox_button_clicked), |
| 2245 event |
2346 event |
| 2246 ); |
2347 ); |
| 2247 } |
2348 } |
| 2248 |
2349 } |
| 2249 return row; |
2350 |
| |
2351 static void update_sublist_item(UiListBox *listbox, UiListBoxSubList *sublist, int index) { |
| |
2352 GtkListBoxRow *row = gtk_list_box_get_row_at_index(listbox->listbox, sublist->startpos + index); |
| |
2353 if(!row) { |
| |
2354 return; |
| |
2355 } |
| |
2356 UiList *list = sublist->var->value; |
| |
2357 if(!list) { |
| |
2358 return; |
| |
2359 } |
| |
2360 |
| |
2361 void *elm = list->get(list, index); |
| |
2362 UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL }; |
| |
2363 if(listbox->getvalue) { |
| |
2364 listbox->getvalue(list, sublist->userdata, elm, index, &item, listbox->getvaluedata); |
| |
2365 } else { |
| |
2366 item.label = strdup(elm); |
| |
2367 } |
| |
2368 |
| |
2369 LISTBOX_ROW_REMOVE_CHILD(row); |
| |
2370 |
| |
2371 listbox_fill_row(listbox, GTK_WIDGET(row), sublist, &item, index); |
| |
2372 |
| |
2373 // cleanup |
| |
2374 free(item.label); |
| |
2375 free(item.icon); |
| |
2376 free(item.button_label); |
| |
2377 free(item.button_icon); |
| |
2378 free(item.badge); |
| 2250 } |
2379 } |
| 2251 |
2380 |
| 2252 void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist, size_t listbox_insert_index) { |
2381 void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist, size_t listbox_insert_index) { |
| 2253 // clear sublist |
2382 // clear sublist |
| 2254 CxIterator r = cxListIterator(sublist->widgets); |
2383 CxIterator r = cxListIterator(sublist->widgets); |
| 2291 } else { |
2420 } else { |
| 2292 item.label = strdup(elm); |
2421 item.label = strdup(elm); |
| 2293 } |
2422 } |
| 2294 |
2423 |
| 2295 // create listbox item |
2424 // create listbox item |
| 2296 GtkWidget *row = create_listbox_row(listbox, sublist, &item, (int)index); |
2425 GtkWidget *row = gtk_list_box_row_new(); |
| |
2426 listbox_fill_row(listbox, row, sublist, &item, (int)index); |
| 2297 if(index == 0) { |
2427 if(index == 0) { |
| 2298 // first row in the sublist, set ui_listbox data to the row |
2428 // first row in the sublist, set ui_listbox data to the row |
| 2299 // which is then used by the headerfunc |
2429 // which is then used by the headerfunc |
| 2300 g_object_set_data(G_OBJECT(row), "ui_listbox", listbox); |
2430 g_object_set_data(G_OBJECT(row), "ui_listbox", listbox); |
| 2301 g_object_set_data(G_OBJECT(row), "ui_listbox_sublist", sublist); |
2431 g_object_set_data(G_OBJECT(row), "ui_listbox_sublist", sublist); |
| 2325 sublist->numitems = cxListSize(sublist->widgets); |
2455 sublist->numitems = cxListSize(sublist->widgets); |
| 2326 } |
2456 } |
| 2327 |
2457 |
| 2328 void ui_listbox_list_update(UiList *list, int i) { |
2458 void ui_listbox_list_update(UiList *list, int i) { |
| 2329 UiListBoxSubList *sublist = list->obj; |
2459 UiListBoxSubList *sublist = list->obj; |
| 2330 ui_listbox_update_sublist(sublist->listbox, sublist, sublist->startpos); |
2460 if(i < 0) { |
| 2331 size_t pos = 0; |
2461 ui_listbox_update_sublist(sublist->listbox, sublist, sublist->startpos); |
| 2332 CxIterator it = cxListIterator(sublist->listbox->sublists); |
2462 size_t pos = 0; |
| 2333 cx_foreach(UiListBoxSubList *, ls, it) { |
2463 CxIterator it = cxListIterator(sublist->listbox->sublists); |
| 2334 ls->startpos = pos; |
2464 cx_foreach(UiListBoxSubList *, ls, it) { |
| 2335 pos += sublist->numitems; |
2465 ls->startpos = pos; |
| 2336 } |
2466 pos += ls->numitems; |
| 2337 |
2467 } |
| |
2468 } else { |
| |
2469 update_sublist_item(sublist->listbox, sublist, i); |
| |
2470 } |
| 2338 } |
2471 } |
| 2339 |
2472 |
| 2340 void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data) { |
2473 void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data) { |
| 2341 UiEventDataExt *data = g_object_get_data(G_OBJECT(row), "ui-listbox-row-eventdata"); |
2474 UiEventDataExt *data = g_object_get_data(G_OBJECT(row), "ui-listbox-row-eventdata"); |
| 2342 if(!data) { |
2475 if(!data) { |