UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2017 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <string.h> 32 #include <stdarg.h> 33 34 #include "../common/context.h" 35 #include "../common/object.h" 36 #include "container.h" 37 38 #include "tree.h" 39 40 41 void* ui_strmodel_getvalue(void *elm, int column) { 42 return column == 0 ? elm : NULL; 43 } 44 45 46 UIWIDGET ui_listview_str(UiObject *obj, UiList *list, ui_callback f, void *udata) { 47 return ui_listview(obj, list, ui_strmodel_getvalue, f, udata); 48 } 49 50 UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) { 51 // create treeview 52 GtkWidget *view = gtk_tree_view_new(); 53 GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); 54 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 0, NULL); 55 gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); 56 57 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); 58 #ifdef UI_GTK3 59 #if GTK_MINOR_VERSION >= 8 60 gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE); 61 #else 62 // TODO: implement for older gtk3 63 #endif 64 #else 65 // TODO: implement for gtk2 66 #endif 67 68 UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); 69 model->getvalue = getvalue; 70 UiList *list = var->value; 71 UiListModel *listmodel = ui_list_model_new(obj, var, model); 72 gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); 73 74 UiListView *listview = malloc(sizeof(UiListView)); 75 listview->obj = obj; 76 listview->widget = view; 77 listview->var = var; 78 listview->model = model; 79 g_signal_connect( 80 view, 81 "destroy", 82 G_CALLBACK(ui_listview_destroy), 83 listview); 84 85 // bind var 86 list->update = ui_listview_update; 87 list->obj = listview; 88 89 // add callback 90 if(f) { 91 UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData)); 92 event->obj = obj; 93 event->userdata = udata; 94 event->activate = f; 95 event->selection = NULL; 96 97 g_signal_connect( 98 view, 99 "row-activated", 100 G_CALLBACK(ui_listview_activate_event), 101 event); 102 } 103 104 // add widget to the current container 105 GtkWidget *scroll_area = gtk_scrolled_window_new(NULL, NULL); 106 gtk_scrolled_window_set_policy( 107 GTK_SCROLLED_WINDOW(scroll_area), 108 GTK_POLICY_AUTOMATIC, 109 GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS 110 gtk_container_add(GTK_CONTAINER(scroll_area), view); 111 112 UiContainer *ct = uic_get_current_container(obj); 113 ct->add(ct, scroll_area, TRUE); 114 115 // ct->current should point to view, not scroll_area, to make it possible 116 // to add a context menu 117 ct->current = view; 118 119 return scroll_area; 120 } 121 122 UIWIDGET ui_listview(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) { 123 UiVar *var = malloc(sizeof(UiVar)); 124 var->value = list; 125 var->type = UI_VAR_SPECIAL; 126 return ui_listview_var(obj, var, getvalue, f, udata); 127 } 128 129 UIWIDGET ui_listview_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata) { 130 UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST); 131 if(var) { 132 return ui_listview_var(obj, var, getvalue, f, udata); 133 } else { 134 // TODO: error 135 } 136 return NULL; 137 } 138 139 static void drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer udata) { 140 printf("drag begin\n"); 141 142 } 143 144 static void drag_end( 145 GtkWidget *widget, 146 GdkDragContext *context, 147 guint time, 148 gpointer udata) 149 { 150 printf("drag end\n"); 151 152 } 153 154 static GtkTargetEntry targetentries[] = 155 { 156 { "STRING", 0, 0 }, 157 { "text/plain", 0, 1 }, 158 { "text/uri-list", 0, 2 }, 159 }; 160 161 UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb) { 162 // create treeview 163 GtkWidget *view = gtk_tree_view_new(); 164 165 int addi = 0; 166 for(int i=0;i<model->columns;i++) { 167 GtkTreeViewColumn *column = NULL; 168 if(model->types[i] == UI_ICON_TEXT) { 169 column = gtk_tree_view_column_new(); 170 gtk_tree_view_column_set_title(column, model->titles[i]); 171 172 GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new(); 173 GtkCellRenderer *textrenderer = gtk_cell_renderer_text_new(); 174 175 gtk_tree_view_column_pack_end(column, textrenderer, TRUE); 176 gtk_tree_view_column_pack_start(column, iconrenderer, FALSE); 177 178 179 gtk_tree_view_column_add_attribute(column, iconrenderer, "pixbuf", i); 180 gtk_tree_view_column_add_attribute(column, textrenderer, "text", i+1); 181 182 addi++; 183 } else { 184 GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); 185 column = gtk_tree_view_column_new_with_attributes( 186 model->titles[i], 187 renderer, 188 "text", 189 i + addi, 190 NULL); 191 } 192 gtk_tree_view_column_set_resizable(column, TRUE); 193 gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); 194 } 195 196 //gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); 197 #ifdef UI_GTK3 198 //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE); 199 #else 200 201 #endif 202 203 UiList *list = var->value; 204 UiListModel *listmodel = ui_list_model_new(obj, var, model); 205 gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); 206 207 //g_signal_connect(view, "drag-begin", G_CALLBACK(drag_begin), NULL); 208 //g_signal_connect(view, "drag-end", G_CALLBACK(drag_end), NULL); 209 210 // add TreeView as observer to the UiList to update the TreeView if the 211 // data changes 212 UiListView *tableview = malloc(sizeof(UiListView)); 213 tableview->obj = obj; 214 tableview->widget = view; 215 tableview->var = var; 216 tableview->model = model; 217 g_signal_connect( 218 view, 219 "destroy", 220 G_CALLBACK(ui_listview_destroy), 221 tableview); 222 223 // bind var 224 list->update = ui_listview_update; 225 list->obj = tableview; 226 227 // add callback 228 UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData)); 229 event->obj = obj; 230 event->activate = cb.activate; 231 event->selection = cb.selection; 232 event->userdata = cb.userdata; 233 if(cb.activate) { 234 g_signal_connect( 235 view, 236 "row-activated", 237 G_CALLBACK(ui_listview_activate_event), 238 event); 239 } 240 if(cb.selection) { 241 GtkTreeSelection *selection = gtk_tree_view_get_selection( 242 GTK_TREE_VIEW(view)); 243 g_signal_connect( 244 selection, 245 "changed", 246 G_CALLBACK(ui_listview_selection_event), 247 event); 248 } 249 // TODO: destroy callback 250 251 252 GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view)); 253 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); 254 255 // add widget to the current container 256 GtkWidget *scroll_area = gtk_scrolled_window_new(NULL, NULL); 257 gtk_scrolled_window_set_policy( 258 GTK_SCROLLED_WINDOW(scroll_area), 259 GTK_POLICY_AUTOMATIC, 260 GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS 261 gtk_container_add(GTK_CONTAINER(scroll_area), view); 262 263 UiContainer *ct = uic_get_current_container(obj); 264 ct->add(ct, scroll_area, TRUE); 265 266 // ct->current should point to view, not scroll_area, to make it possible 267 // to add a context menu 268 ct->current = view; 269 270 return scroll_area; 271 } 272 273 UIWIDGET ui_table(UiObject *obj, UiList *list, UiModel *model, UiListCallbacks cb) { 274 UiVar *var = malloc(sizeof(UiVar)); 275 var->value = list; 276 var->type = UI_VAR_SPECIAL; 277 return ui_table_var(obj, var, model, cb); 278 } 279 280 UIWIDGET ui_table_nv(UiObject *obj, char *varname, UiModel *model, UiListCallbacks cb) { 281 UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST); 282 if(var) { 283 return ui_table_var(obj, var, model, cb); 284 } else { 285 // TODO: error 286 } 287 return NULL; 288 } 289 290 GtkWidget* ui_get_tree_widget(UIWIDGET widget) { 291 GList *c = gtk_container_get_children(GTK_CONTAINER(widget)); 292 if(c) { 293 return c->data; 294 } 295 return NULL; 296 } 297 298 static char** targets2array(char *target0, va_list ap, int *nelm) { 299 int al = 16; 300 char **targets = calloc(16, sizeof(char*)); 301 targets[0] = target0; 302 303 int i = 1; 304 char *target; 305 while((target = va_arg(ap, char*)) != NULL) { 306 if(i >= al) { 307 al *= 2; 308 targets = realloc(targets, al*sizeof(char*)); 309 } 310 targets[i] = target; 311 i++; 312 } 313 314 *nelm = i; 315 return targets; 316 } 317 318 static GtkTargetEntry* targetstr2gtktargets(char **str, int nelm) { 319 GtkTargetEntry *targets = calloc(nelm, sizeof(GtkTargetEntry)); 320 for(int i=0;i<nelm;i++) { 321 targets[i].target = str[i]; 322 } 323 return targets; 324 } 325 326 void ui_table_dragsource(UIWIDGET tablewidget, int actions, char *target0, ...) { 327 va_list ap; 328 va_start(ap, target0); 329 int nelm; 330 char **targets = targets2array(target0, ap, &nelm); 331 va_end(ap); 332 ui_table_dragsource_a(tablewidget, actions, targets, nelm); 333 free(targets); 334 } 335 336 void ui_table_dragsource_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) { 337 GtkTargetEntry* t = targetstr2gtktargets(targets, nelm); 338 gtk_tree_view_enable_model_drag_source( 339 GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)), 340 GDK_BUTTON1_MASK, 341 t, 342 nelm, 343 GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK); 344 free(t); 345 } 346 347 void ui_table_dragdest(UIWIDGET tablewidget, int actions, char *target0, ...) { 348 va_list ap; 349 va_start(ap, target0); 350 int nelm; 351 char **targets = targets2array(target0, ap, &nelm); 352 va_end(ap); 353 ui_table_dragdest_a(tablewidget, actions, targets, nelm); 354 free(targets); 355 } 356 357 void ui_table_dragdest_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) { 358 GtkTargetEntry* t = targetstr2gtktargets(targets, nelm); 359 gtk_tree_view_enable_model_drag_dest( 360 GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)), 361 t, 362 nelm, 363 GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK); 364 free(t); 365 } 366 367 void ui_listview_update(UiList *list, int i) { 368 UiListView *view = list->obj; 369 UiListModel *model = ui_list_model_new(view->obj, view->var, view->model); 370 gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(model)); 371 g_object_unref(G_OBJECT(model)); 372 // TODO: free old model 373 } 374 375 void ui_listview_destroy(GtkWidget *w, UiListView *v) { 376 gtk_tree_view_set_model(GTK_TREE_VIEW(w), NULL); 377 ui_destroy_boundvar(v->obj->ctx, v->var); 378 // TODO: destroy model? 379 free(v); 380 } 381 382 void ui_combobox_destroy(GtkWidget *w, UiListView *v) { 383 gtk_combo_box_set_model(GTK_COMBO_BOX(w), NULL); 384 ui_destroy_boundvar(v->obj->ctx, v->var); 385 // TODO: destroy model? 386 free(v); 387 } 388 389 390 void ui_listview_activate_event( 391 GtkTreeView *treeview, 392 GtkTreePath *path, 393 GtkTreeViewColumn *column, 394 UiTreeEventData *event) 395 { 396 UiListSelection *selection = ui_listview_selection( 397 gtk_tree_view_get_selection(treeview), 398 event); 399 400 UiEvent e; 401 e.obj = event->obj; 402 e.window = event->obj->window; 403 e.document = event->obj->ctx->document; 404 e.eventdata = selection; 405 e.intval = selection->count > 0 ? selection->rows[0] : -1; 406 event->activate(&e, event->userdata); 407 408 if(selection->count > 0) { 409 free(selection->rows); 410 } 411 free(selection); 412 } 413 414 void ui_listview_selection_event( 415 GtkTreeSelection *treeselection, 416 UiTreeEventData *event) 417 { 418 UiListSelection *selection = ui_listview_selection(treeselection, event); 419 420 UiEvent e; 421 e.obj = event->obj; 422 e.window = event->obj->window; 423 e.document = event->obj->ctx->document; 424 e.eventdata = selection; 425 e.intval = selection->count > 0 ? selection->rows[0] : -1; 426 event->selection(&e, event->userdata); 427 428 if(selection->count > 0) { 429 free(selection->rows); 430 } 431 free(selection); 432 } 433 434 UiListSelection* ui_listview_selection( 435 GtkTreeSelection *selection, 436 UiTreeEventData *event) 437 { 438 GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL); 439 440 UiListSelection *ls = malloc(sizeof(UiListSelection)); 441 ls->count = g_list_length(rows); 442 ls->rows = calloc(ls->count, sizeof(int)); 443 GList *r = rows; 444 int i = 0; 445 while(r) { 446 GtkTreePath *path = r->data; 447 ls->rows[i] = ui_tree_path_list_index(path); 448 r = r->next; 449 i++; 450 } 451 return ls; 452 } 453 454 int ui_tree_path_list_index(GtkTreePath *path) { 455 int depth = gtk_tree_path_get_depth(path); 456 if(depth == 0) { 457 fprintf(stderr, "UiError: treeview selection: depth == 0\n"); 458 return -1; 459 } 460 int *indices = gtk_tree_path_get_indices(path); 461 return indices[depth - 1]; 462 } 463 464 465 /* --------------------------- ComboBox --------------------------- */ 466 467 UIWIDGET ui_combobox_str(UiObject *obj, UiList *list, ui_callback f, void *udata) { 468 return ui_combobox(obj, list, ui_strmodel_getvalue, f, udata); 469 } 470 471 UIWIDGET ui_combobox(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) { 472 UiVar *var = malloc(sizeof(UiVar)); 473 var->value = list; 474 var->type = UI_VAR_SPECIAL; 475 return ui_combobox_var(obj, var, getvalue, f, udata); 476 } 477 478 UIWIDGET ui_combobox_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata) { 479 UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST); 480 if(var) { 481 return ui_combobox_var(obj, var, getvalue, f, udata); 482 } else { 483 // TODO: error 484 } 485 return NULL; 486 } 487 488 UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) { 489 UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); 490 model->getvalue = getvalue; 491 UiListModel *listmodel = ui_list_model_new(obj, var, model); 492 493 GtkWidget *combobox = ui_create_combobox(obj, listmodel, f, udata); 494 UiContainer *ct = uic_get_current_container(obj); 495 ct->add(ct, combobox, FALSE); 496 return combobox; 497 } 498 499 GtkWidget* ui_create_combobox(UiObject *obj, UiListModel *model, ui_callback f, void *udata) { 500 GtkWidget *combobox = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model)); 501 502 UiListView *uicbox = malloc(sizeof(UiListView)); 503 uicbox->obj = obj; 504 uicbox->widget = combobox; 505 uicbox->var = model->var; 506 uicbox->model = model->model; 507 508 g_signal_connect( 509 combobox, 510 "destroy", 511 G_CALLBACK(ui_combobox_destroy), 512 uicbox); 513 514 // bind var 515 UiList *list = model->var->value; 516 list->update = ui_combobox_modelupdate; 517 list->obj = uicbox; 518 519 GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); 520 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE); 521 gtk_cell_layout_set_attributes( 522 GTK_CELL_LAYOUT(combobox), 523 renderer, 524 "text", 525 0, 526 NULL); 527 gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0); 528 529 // add callback 530 if(f) { 531 UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData)); 532 event->obj = obj; 533 event->userdata = udata; 534 event->callback = f; 535 event->value = 0; 536 537 g_signal_connect( 538 combobox, 539 "changed", 540 G_CALLBACK(ui_combobox_change_event), 541 event); 542 } 543 544 return combobox; 545 } 546 547 void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) { 548 UiEvent event; 549 event.obj = e->obj; 550 event.window = event.obj->window; 551 event.document = event.obj->ctx->document; 552 event.eventdata = NULL; 553 event.intval = gtk_combo_box_get_active(widget); 554 e->callback(&event, e->userdata); 555 } 556 557 void ui_combobox_modelupdate(UiList *list, int i) { 558 UiListView *view = list->obj; 559 UiListModel *model = ui_list_model_new(view->obj, view->var, view->model); 560 gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(model)); 561 } 562 563