treeview.c (36602B)
1 /************************************************************************ 2 * treeview.c GtkTreeView (and friends) implementation for gtkport * 3 * Copyright (C) 1998-2021 Ben Webb * 4 * Email: benwebb@users.sf.net * 5 * WWW: https://dopewars.sourceforge.io/ * 6 * * 7 * This program is free software; you can redistribute it and/or * 8 * modify it under the terms of the GNU General Public License * 9 * as published by the Free Software Foundation; either version 2 * 10 * of the License, or (at your option) any later version. * 11 * * 12 * This program is distributed in the hope that it will be useful, * 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 * GNU General Public License for more details. * 16 * * 17 * You should have received a copy of the GNU General Public License * 18 * along with this program; if not, write to the Free Software * 19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, * 20 * MA 02111-1307, USA. * 21 ************************************************************************/ 22 23 #ifdef HAVE_CONFIG_H 24 #include <config.h> 25 #endif 26 27 #include "gtkport.h" 28 29 #ifdef CYGWIN 30 31 #include <winsock2.h> 32 #include <windows.h> 33 #include <commctrl.h> 34 35 #include "unicodewrap.h" 36 37 #define LISTITEMHPACK 3 38 #define LISTHEADERPACK 6 39 40 static const gchar *WC_GTKTREEVIEWHDR = "WC_GTKTREEVIEWHDR"; 41 42 static WNDPROC wpOrigListProc; 43 44 static void gtk_tree_view_size_request(GtkWidget *widget, 45 GtkRequisition *requisition); 46 static void gtk_tree_view_set_size(GtkWidget *widget, 47 GtkAllocation *allocation); 48 static gboolean gtk_tree_view_wndproc(GtkWidget *widget, UINT msg, 49 WPARAM wParam, LPARAM lParam, gboolean *dodef); 50 static void gtk_tree_view_realize(GtkWidget *widget); 51 static void gtk_tree_view_destroy(GtkWidget *widget); 52 static void gtk_tree_view_show(GtkWidget *widget); 53 static void gtk_tree_view_hide(GtkWidget *widget); 54 static void gtk_tree_view_draw_row(GtkTreeView *tv, LPDRAWITEMSTRUCT lpdis); 55 static void gtk_tree_view_update_selection(GtkWidget *widget); 56 static void gtk_tree_view_update_widths(GtkTreeView *tv, GtkTreeModel *model, 57 GtkListStoreRow *row); 58 static void gtk_tree_view_update_all_widths(GtkTreeView *tv); 59 static void gtk_tree_view_do_auto_resize(GtkTreeView *tv); 60 static void gtk_tree_view_set_column_width(GtkTreeView *tv, gint column, 61 gint width); 62 static void gtk_tree_view_set_column_width_full(GtkTreeView *tv, gint column, 63 gint width, 64 gboolean ResizeHeader); 65 static void gtk_tree_view_click_column(GtkWidget *widget, gint column); 66 67 static GtkSignalType GtkTreeViewSignals[] = { 68 {"size_request", gtk_marshal_VOID__GPOIN, gtk_tree_view_size_request}, 69 {"set_size", gtk_marshal_VOID__GPOIN, gtk_tree_view_set_size}, 70 {"realize", gtk_marshal_VOID__VOID, gtk_tree_view_realize}, 71 {"destroy", gtk_marshal_VOID__VOID, gtk_tree_view_destroy}, 72 {"click-column", gtk_marshal_VOID__GINT, gtk_tree_view_click_column}, 73 {"changed", gtk_marshal_VOID__VOID, NULL}, 74 {"show", gtk_marshal_VOID__VOID, gtk_tree_view_show}, 75 {"hide", gtk_marshal_VOID__VOID, gtk_tree_view_hide}, 76 {"", NULL, NULL} 77 }; 78 79 static GtkClass GtkTreeViewClass = { 80 "tree_view", &GtkContainerClass, sizeof(GtkTreeView), GtkTreeViewSignals, 81 gtk_tree_view_wndproc 82 }; 83 84 static GtkClass GtkListStoreClass = { 85 "list_store", &GtkObjectClass, sizeof(GtkListStore), NULL, NULL 86 }; 87 88 static void SetTreeViewHeaderSize(GtkTreeView *clist) 89 { 90 RECT rc; 91 HWND hWnd; 92 int width; 93 94 hWnd = GTK_WIDGET(clist)->hWnd; 95 clist->scrollpos = GetScrollPos(hWnd, SB_HORZ); 96 97 GetWindowRect(hWnd, &rc); 98 width = (int)SendMessageW(hWnd, LB_GETHORIZONTALEXTENT, 0, 0); 99 width = MAX(width, rc.right - rc.left) + 100; 100 101 SetWindowPos(clist->header, HWND_TOP, -clist->scrollpos, 0, 102 width, clist->header_size, SWP_NOZORDER); 103 } 104 105 static LRESULT APIENTRY ListWndProc(HWND hwnd, UINT msg, WPARAM wParam, 106 LPARAM lParam) 107 { 108 LRESULT retval; 109 GtkWidget *widget; 110 111 widget = GTK_WIDGET(GetWindowLongPtr(hwnd, GWLP_USERDATA)); 112 retval = CallWindowProcW(wpOrigListProc, hwnd, msg, wParam, lParam); 113 114 if (msg == WM_HSCROLL && widget) { 115 GtkTreeView *clist = GTK_TREE_VIEW(widget); 116 SetTreeViewHeaderSize(clist); 117 } 118 119 return retval; 120 } 121 122 gboolean gtk_tree_view_wndproc(GtkWidget *widget, UINT msg, WPARAM wParam, 123 LPARAM lParam, gboolean *dodef) 124 { 125 LPDRAWITEMSTRUCT lpdis; 126 HD_NOTIFYA FAR *phdr; 127 HD_NOTIFYW FAR *phdrw; 128 NMHDR *nmhdr; 129 130 switch(msg) { 131 case WM_COMMAND: 132 if (lParam && HIWORD(wParam) == LBN_SELCHANGE) { 133 gtk_tree_view_update_selection(widget); 134 return FALSE; 135 } 136 break; 137 case WM_DRAWITEM: 138 lpdis = (LPDRAWITEMSTRUCT)lParam; 139 if (lpdis) { 140 gtk_tree_view_draw_row(GTK_TREE_VIEW(widget), lpdis); 141 *dodef = FALSE; 142 return TRUE; 143 } 144 break; 145 case WM_NOTIFY: 146 nmhdr = (NMHDR *)lParam; 147 if (nmhdr) { 148 switch(nmhdr->code) { 149 case HDN_ENDTRACKA: 150 phdr = (HD_NOTIFYA FAR *)lParam; 151 gtk_tree_view_set_column_width_full(GTK_TREE_VIEW(widget), phdr->iItem, 152 phdr->pitem->cxy, FALSE); 153 return FALSE; 154 case HDN_ENDTRACKW: 155 phdrw = (HD_NOTIFYW FAR *)lParam; 156 gtk_tree_view_set_column_width_full(GTK_TREE_VIEW(widget), phdrw->iItem, 157 phdrw->pitem->cxy, FALSE); 158 return FALSE; 159 case HDN_ITEMCLICKA: 160 phdr = (HD_NOTIFYA FAR *)lParam; 161 gtk_signal_emit(G_OBJECT(widget), "click-column", (gint)phdr->iItem); 162 return FALSE; 163 case HDN_ITEMCLICKW: 164 phdrw = (HD_NOTIFYW FAR *)lParam; 165 gtk_signal_emit(G_OBJECT(widget), "click-column", (gint)phdrw->iItem); 166 return FALSE; 167 default: 168 break; 169 } 170 } 171 break; 172 } 173 174 return FALSE; 175 } 176 177 static void gtk_tree_view_set_extent(GtkTreeView *tv) 178 { 179 HWND hWnd; 180 181 hWnd = GTK_WIDGET(tv)->hWnd; 182 if (hWnd) { 183 GSList *colpt; 184 int width = 0; 185 186 for (colpt = tv->columns; colpt; colpt = g_slist_next(colpt)) { 187 GtkTreeViewColumn *col = colpt->data; 188 width += col->width; 189 } 190 SendMessageW(hWnd, LB_SETHORIZONTALEXTENT, (WPARAM)width, 0); 191 SetTreeViewHeaderSize(tv); 192 } 193 } 194 195 void gtk_tree_view_set_size(GtkWidget *widget, GtkAllocation *allocation) 196 { 197 GtkTreeView *clist = GTK_TREE_VIEW(widget); 198 199 gtk_container_set_size(widget, allocation); 200 if (clist->header) { 201 POINT pt; 202 pt.x = allocation->x; 203 pt.y = allocation->y; 204 MapWidgetOrigin(widget, &pt); 205 SetWindowPos(clist->scrollwin, HWND_TOP, pt.x, pt.y, 206 allocation->width, clist->header_size, SWP_NOZORDER); 207 allocation->y += clist->header_size - 1; 208 allocation->height -= clist->header_size - 1; 209 } 210 gtk_tree_view_set_extent(clist); 211 } 212 213 GtkWidget *gtk_tree_view_new(void) 214 { 215 GtkTreeView *view; 216 217 view = GTK_TREE_VIEW(GtkNewObject(&GtkTreeViewClass)); 218 view->model = NULL; 219 view->scrollpos = 0; 220 view->columns = NULL; 221 view->headers_clickable = TRUE; 222 view->mode = GTK_SELECTION_SINGLE; 223 view->selection = NULL; 224 return GTK_WIDGET(view); 225 } 226 227 GtkTreeSelection *gtk_tree_view_get_selection(GtkTreeView *tree_view) 228 { 229 /* The selection *is* the tree view */ 230 return tree_view; 231 } 232 233 void gtk_tree_view_size_request(GtkWidget *widget, GtkRequisition *requisition) 234 { 235 SIZE size; 236 237 if (GetTextSize(widget->hWnd, "Sample text", &size, defFont)) { 238 requisition->width = size.cx; 239 requisition->height = size.cy * 6 + 12; 240 } 241 } 242 243 void gtk_tree_view_realize(GtkWidget *widget) 244 { 245 HWND Parent, header, scrollwin; 246 HD_LAYOUT hdl; 247 HD_ITEM hdi; 248 RECT rcParent; 249 WINDOWPOS wp; 250 GtkTreeView *tv = GTK_TREE_VIEW(widget); 251 GSList *colpt; 252 gint i; 253 254 gtk_container_realize(widget); 255 Parent = gtk_get_parent_hwnd(widget); 256 GTK_WIDGET_SET_FLAGS(widget, GTK_CAN_FOCUS); 257 rcParent.left = rcParent.top = 0; 258 rcParent.right = rcParent.bottom = 800; 259 scrollwin = myCreateWindow(WC_GTKTREEVIEWHDR, NULL, WS_CHILD | WS_BORDER, 260 0, 0, 0, 0, Parent, NULL, hInst, NULL); 261 SetWindowLongPtr(scrollwin, GWLP_USERDATA, (LONG_PTR)widget); 262 header = myCreateWindowEx(0, WC_HEADER, NULL, 263 WS_CHILD | HDS_HORZ | WS_VISIBLE 264 | (tv->headers_clickable ? HDS_BUTTONS : 0), 265 0, 0, 0, 0, scrollwin, NULL, hInst, NULL); 266 SetWindowLongPtr(header, GWLP_USERDATA, (LONG_PTR)widget); 267 tv->header = header; 268 tv->scrollwin = scrollwin; 269 gtk_set_default_font(header); 270 hdl.prc = &rcParent; 271 hdl.pwpos = ℘ 272 SendMessageW(header, HDM_LAYOUT, 0, (LPARAM)&hdl); 273 tv->header_size = wp.cy; 274 widget->hWnd = myCreateWindowEx(WS_EX_CLIENTEDGE, "LISTBOX", "", 275 WS_CHILD | WS_TABSTOP | WS_VSCROLL 276 | WS_HSCROLL | LBS_OWNERDRAWFIXED 277 | LBS_NOTIFY, 0, 0, 0, 0, Parent, NULL, 278 hInst, NULL); 279 /* Subclass the window */ 280 wpOrigListProc = (WNDPROC)SetWindowLongPtrW(widget->hWnd, GWLP_WNDPROC, 281 (LONG_PTR)ListWndProc); 282 gtk_set_default_font(widget->hWnd); 283 284 if (tv->model) { 285 for (i = 0; i < tv->model->rows->len; ++i) { 286 SendMessageW(widget->hWnd, LB_ADDSTRING, 0, 1); 287 } 288 } 289 gtk_tree_view_update_all_widths(tv); 290 291 for (colpt = tv->columns, i = 0; colpt; colpt = g_slist_next(colpt), ++i) { 292 GtkTreeViewColumn *col = colpt->data; 293 if (col->auto_resize) { 294 col->width = col->optimal_width; 295 } 296 hdi.mask = HDI_TEXT | HDI_FORMAT | HDI_WIDTH; 297 hdi.pszText = col->title; 298 if (hdi.pszText) { 299 if (!g_slist_next(colpt)) 300 hdi.cxy = 9000; 301 else 302 hdi.cxy = col->width; 303 hdi.cchTextMax = strlen(hdi.pszText); 304 hdi.fmt = HDF_LEFT | HDF_STRING; 305 myHeader_InsertItem(header, i + 1, &hdi); 306 } 307 } 308 } 309 310 static void gtk_list_store_row_free(GtkListStoreRow *row, GtkListStore *store) 311 { 312 int i; 313 for (i = 0; i < store->ncols; ++i) { 314 if (store->coltype[i] == G_TYPE_STRING) { 315 g_free(row->data[i]); 316 } 317 } 318 } 319 320 void gtk_list_store_clear(GtkListStore *list_store) 321 { 322 guint i; 323 for (i = 0; i < list_store->rows->len; ++i) { 324 GtkListStoreRow *row = &g_array_index(list_store->rows, GtkListStoreRow, i); 325 gtk_list_store_row_free(row, list_store); 326 } 327 g_array_set_size(list_store->rows, 0); 328 list_store->need_sort = FALSE; /* an empty store is sorted */ 329 330 if (list_store->view) { 331 HWND hWnd; 332 gtk_tree_view_update_all_widths(list_store->view); 333 hWnd = GTK_WIDGET(list_store->view)->hWnd; 334 if (hWnd) { 335 SendMessageW(hWnd, LB_RESETCONTENT, 0, 0); 336 } 337 } 338 } 339 340 void gtk_list_store_insert(GtkListStore *list_store, GtkTreeIter *iter, 341 gint position) 342 { 343 GtkListStoreRow row; 344 /* Add a new empty row to the store and return a pointer to it */ 345 row.data = g_new0(gpointer, list_store->ncols); 346 if (position < 0) { 347 g_array_append_val(list_store->rows, row); 348 *iter = list_store->rows->len - 1; 349 } else { 350 g_array_insert_val(list_store->rows, position, row); 351 *iter = position; 352 } 353 } 354 355 void gtk_list_store_append(GtkListStore *list_store, GtkTreeIter *iter) 356 { 357 gtk_list_store_insert(list_store, iter, -1); 358 } 359 360 void gtk_list_store_set(GtkListStore *list_store, GtkTreeIter *iter, ...) 361 { 362 va_list ap; 363 int colind; 364 gboolean new_row = TRUE; 365 GtkListStoreRow *row = &g_array_index(list_store->rows, GtkListStoreRow, 366 *iter); 367 list_store->need_sort = TRUE; 368 369 va_start(ap, iter); 370 while ((colind = va_arg(ap, int)) >= 0) { 371 switch(list_store->coltype[colind]) { 372 case G_TYPE_STRING: 373 g_free(row->data[colind]); /* Free any existing string */ 374 if (row->data[colind]) { 375 new_row = FALSE; 376 } 377 row->data[colind] = g_strdup(va_arg(ap, const char*)); 378 break; 379 case G_TYPE_UINT: 380 row->data[colind] = GUINT_TO_POINTER(va_arg(ap, unsigned)); 381 break; 382 case G_TYPE_INT: 383 row->data[colind] = GINT_TO_POINTER(va_arg(ap, int)); 384 break; 385 case G_TYPE_POINTER: 386 row->data[colind] = va_arg(ap, gpointer); 387 break; 388 } 389 } 390 va_end(ap); 391 392 if (list_store->view) { 393 GtkWidget *widget = GTK_WIDGET(list_store->view); 394 395 gtk_tree_view_update_widths(list_store->view, list_store, row); 396 gtk_tree_view_do_auto_resize(list_store->view); 397 398 if (GTK_WIDGET_REALIZED(widget)) { 399 HWND hWnd = widget->hWnd; 400 if (new_row) { 401 SendMessageW(hWnd, LB_INSERTSTRING, (WPARAM)*iter, 1); 402 } else { 403 InvalidateRect(hWnd, NULL, FALSE); 404 } 405 } 406 } 407 } 408 409 void gtk_list_store_swap(GtkListStore *store, GtkTreeIter *a, GtkTreeIter *b) 410 { 411 GtkTreeIter tmp; 412 GtkListStoreRow rowa = g_array_index(store->rows, GtkListStoreRow, *a); 413 GtkListStoreRow rowb = g_array_index(store->rows, GtkListStoreRow, *b); 414 415 g_array_index(store->rows, GtkListStoreRow, *a) = rowb; 416 g_array_index(store->rows, GtkListStoreRow, *b) = rowa; 417 store->need_sort = TRUE; 418 419 /* Swap the iterators too since in our implementation they are just row 420 indices */ 421 tmp = *a; 422 *a = *b; 423 *b = tmp; 424 } 425 426 void gtk_tree_model_get(GtkTreeModel *tree_model, GtkTreeIter *iter, ...) 427 { 428 va_list ap; 429 char **strpt; 430 unsigned *uintpt; 431 int *intpt; 432 gpointer *ptpt; 433 int colind; 434 GtkListStoreRow *row = &g_array_index(tree_model->rows, GtkListStoreRow, 435 *iter); 436 437 va_start(ap, iter); 438 while ((colind = va_arg(ap, int)) >= 0) { 439 switch(tree_model->coltype[colind]) { 440 case G_TYPE_STRING: 441 strpt = va_arg(ap, char **); 442 *strpt = g_strdup(row->data[colind]); 443 break; 444 case G_TYPE_UINT: 445 uintpt = va_arg(ap, unsigned *); 446 *uintpt = GPOINTER_TO_UINT(row->data[colind]); 447 break; 448 case G_TYPE_INT: 449 intpt = va_arg(ap, int *); 450 *intpt = GPOINTER_TO_INT(row->data[colind]); 451 break; 452 case G_TYPE_POINTER: 453 ptpt = va_arg(ap, gpointer *); 454 *ptpt = row->data[colind]; 455 break; 456 } 457 } 458 va_end(ap); 459 } 460 461 gboolean gtk_tree_model_iter_nth_child(GtkTreeModel *tree_model, 462 GtkTreeIter *iter, 463 GtkTreeIter *parent, gint n) 464 { 465 /* We only work with one level (lists) for now */ 466 g_assert(parent == NULL); 467 *iter = n; 468 return TRUE; 469 } 470 471 gint gtk_tree_model_iter_n_children(GtkTreeModel *tree_model, 472 GtkTreeIter *iter) 473 { 474 /* We only work with one level (lists) for now */ 475 if (iter) { 476 return 1; 477 } else { 478 return tree_model->rows->len; 479 } 480 } 481 482 483 static void gtk_tree_view_column_free(gpointer data) 484 { 485 GtkTreeViewColumn *col = data; 486 g_free(col->title); 487 g_free(col); 488 } 489 490 void gtk_tree_model_free(GtkTreeModel *model) 491 { 492 gtk_list_store_clear(model); /* Remove all rows */ 493 g_array_free(model->rows, TRUE); 494 g_array_free(model->sort_func, TRUE); 495 g_free(model->coltype); 496 g_free(model); 497 } 498 499 void gtk_tree_view_destroy(GtkWidget *widget) 500 { 501 GtkTreeView *view = GTK_TREE_VIEW(widget); 502 g_slist_free_full(view->columns, gtk_tree_view_column_free); 503 view->columns = NULL; 504 if (view->model) { 505 gtk_tree_model_free(view->model); 506 } 507 view->model = NULL; 508 } 509 510 void gtk_tree_view_click_column(GtkWidget *widget, gint column) 511 { 512 GtkTreeView *view = GTK_TREE_VIEW(widget); 513 GtkTreeViewColumn *col = g_slist_nth_data(view->columns, column); 514 GtkListStore *model = view->model; 515 if (!model || !view->headers_clickable) return; 516 517 if (col->sort_column_id == model->sort_column_id) { 518 /* toggle order */ 519 if (model->sort_order == GTK_SORT_ASCENDING) { 520 model->sort_order = GTK_SORT_DESCENDING; 521 } else { 522 model->sort_order = GTK_SORT_ASCENDING; 523 } 524 } else { 525 model->sort_column_id = col->sort_column_id; 526 model->sort_order = GTK_SORT_ASCENDING; 527 } 528 model->need_sort = TRUE; 529 gtk_tree_view_sort(view); 530 } 531 532 void gtk_tree_view_show(GtkWidget *widget) 533 { 534 if (GTK_WIDGET_REALIZED(widget)) { 535 ShowWindow(GTK_TREE_VIEW(widget)->scrollwin, SW_SHOWNORMAL); 536 } 537 } 538 539 void gtk_tree_view_hide(GtkWidget *widget) 540 { 541 if (GTK_WIDGET_REALIZED(widget)) { 542 ShowWindow(GTK_TREE_VIEW(widget)->scrollwin, SW_HIDE); 543 } 544 } 545 546 /* Draw an individual cell (row+column) */ 547 static void draw_cell_text(GtkTreeViewColumn *col, GtkTreeModel *model, 548 LPDRAWITEMSTRUCT lpdis, GtkListStoreRow *row, 549 RECT *rcCol) 550 { 551 UINT align; 552 char *val; 553 int modcol = col->model_column; 554 /* Convert float 0.0, 0.5 or 1.0 into int, allow for some rounding error */ 555 switch((int)(col->xalign * 10. + 0.1)) { 556 case 10: 557 align = DT_RIGHT; 558 break; 559 case 5: 560 align = DT_CENTER; 561 break; 562 default: 563 align = DT_LEFT; 564 break; 565 } 566 align |= DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS; 567 568 switch(model->coltype[modcol]) { 569 case G_TYPE_STRING: 570 if (row->data[modcol]) { 571 myDrawText(lpdis->hDC, row->data[modcol], -1, rcCol, align); 572 } 573 break; 574 case G_TYPE_UINT: 575 val = g_strdup_printf("%u", GPOINTER_TO_UINT(row->data[modcol])); 576 myDrawText(lpdis->hDC, val, -1, rcCol, align); 577 g_free(val); 578 break; 579 case G_TYPE_INT: 580 val = g_strdup_printf("%d", GPOINTER_TO_INT(row->data[modcol])); 581 myDrawText(lpdis->hDC, val, -1, rcCol, align); 582 g_free(val); 583 break; 584 } 585 } 586 587 void gtk_tree_view_draw_row(GtkTreeView *tv, LPDRAWITEMSTRUCT lpdis) 588 { 589 HBRUSH bkgrnd; 590 COLORREF textcol, oldtextcol; 591 RECT rcCol; 592 int oldbkmode; 593 guint nrows; 594 gint CurrentX, right; 595 GtkListStoreRow *row; 596 597 if (lpdis->itemState & ODS_SELECTED) { 598 bkgrnd = (HBRUSH)(1 + COLOR_HIGHLIGHT); 599 textcol = (COLORREF)GetSysColor(COLOR_HIGHLIGHTTEXT); 600 } else { 601 bkgrnd = (HBRUSH)(1 + COLOR_WINDOW); 602 textcol = (COLORREF)GetSysColor(COLOR_WINDOWTEXT); 603 } 604 oldtextcol = SetTextColor(lpdis->hDC, textcol); 605 oldbkmode = SetBkMode(lpdis->hDC, TRANSPARENT); 606 FillRect(lpdis->hDC, &lpdis->rcItem, bkgrnd); 607 608 nrows = tv->model ? tv->model->rows->len : 0; 609 if (lpdis->itemID >= 0 && lpdis->itemID < nrows) { 610 int width; 611 GSList *colpt; 612 row = &g_array_index(tv->model->rows, GtkListStoreRow, lpdis->itemID); 613 width = CurrentX = 0; 614 for (colpt = tv->columns; colpt; colpt = g_slist_next(colpt)) { 615 GtkTreeViewColumn *col = colpt->data; 616 width += col->width; 617 } 618 right = MAX(lpdis->rcItem.right, width); 619 rcCol.top = lpdis->rcItem.top; 620 rcCol.bottom = lpdis->rcItem.bottom; 621 if (row->data) 622 for (colpt = tv->columns; colpt; colpt = g_slist_next(colpt)) { 623 GtkTreeViewColumn *col = colpt->data; 624 rcCol.left = CurrentX + LISTITEMHPACK; 625 CurrentX += col->width; 626 rcCol.right = CurrentX - LISTITEMHPACK; 627 if (rcCol.left > right) 628 rcCol.left = right; 629 if (rcCol.right > right - LISTITEMHPACK) 630 rcCol.right = right - LISTITEMHPACK; 631 if (!g_slist_next(colpt)) 632 rcCol.right = right - LISTITEMHPACK; 633 draw_cell_text(col, tv->model, lpdis, row, &rcCol); 634 } 635 } 636 637 SetTextColor(lpdis->hDC, oldtextcol); 638 SetBkMode(lpdis->hDC, oldbkmode); 639 if (lpdis->itemState & ODS_FOCUS) { 640 DrawFocusRect(lpdis->hDC, &lpdis->rcItem); 641 } 642 } 643 644 void gtk_tree_view_do_auto_resize(GtkTreeView *tv) 645 { 646 GSList *colpt; 647 gint i; 648 649 for (colpt = tv->columns, i = 0; colpt; colpt = g_slist_next(colpt), i++) { 650 GtkTreeViewColumn *col = colpt->data; 651 if (col->auto_resize) { 652 gtk_tree_view_set_column_width(tv, i, col->optimal_width); 653 } 654 } 655 } 656 657 gint gtk_tree_view_optimal_column_width(GtkTreeView *tv, gint column) 658 { 659 GtkTreeViewColumn *col = g_slist_nth_data(tv->columns, column); 660 return col->optimal_width; 661 } 662 663 void gtk_tree_view_update_all_widths(GtkTreeView *tv) 664 { 665 SIZE size; 666 HWND header; 667 gint i; 668 669 header = tv->header; 670 if (header) { 671 GSList *colpt; 672 for (colpt = tv->columns, i = 0; colpt; colpt = g_slist_next(colpt), i++) { 673 GtkTreeViewColumn *col = colpt->data; 674 if (GetTextSize(header, col->title, &size, defFont)) { 675 int new_width = size.cx + 4 + 2 * LISTHEADERPACK; 676 col->width = MAX(col->width, new_width); 677 col->optimal_width = MAX(col->optimal_width, new_width); 678 } 679 } 680 } 681 682 if (tv->model) { 683 for (i = 0; i < tv->model->rows->len; ++i) { 684 GtkListStoreRow *row = &g_array_index(tv->model->rows, 685 GtkListStoreRow, i); 686 gtk_tree_view_update_widths(tv, tv->model, row); 687 } 688 } 689 690 gtk_tree_view_set_extent(tv); 691 } 692 693 void gtk_tree_view_update_widths(GtkTreeView *tv, GtkTreeModel *model, 694 GtkListStoreRow *row) 695 { 696 SIZE size; 697 GSList *colpt; 698 HWND hWnd; 699 700 hWnd = GTK_WIDGET(tv)->hWnd; 701 if (!hWnd) 702 return; 703 for (colpt = tv->columns; colpt; colpt = g_slist_next(colpt)) { 704 GtkTreeViewColumn *col = colpt->data; 705 int modcol = col->model_column; 706 char *text; 707 switch (model->coltype[modcol]) { 708 case G_TYPE_STRING: 709 text = row->data[modcol]; 710 break; 711 case G_TYPE_UINT: 712 case G_TYPE_INT: 713 text = "9999"; /* hack */ 714 break; 715 default: 716 text = NULL; 717 } 718 if (text && GetTextSize(hWnd, text, &size, defFont)) { 719 int new_width = size.cx + 4 + 2 * LISTITEMHPACK; 720 col->optimal_width = MAX(col->optimal_width, new_width); 721 } 722 } 723 } 724 725 gboolean gtk_list_store_remove(GtkListStore *list_store, GtkTreeIter *iter) 726 { 727 gint rowind = *iter; 728 if (rowind >= 0 && rowind < list_store->rows->len) { 729 GtkListStoreRow *row = &g_array_index(list_store->rows, 730 GtkListStoreRow, rowind); 731 gtk_list_store_row_free(row, list_store); 732 g_array_remove_index(list_store->rows, rowind); 733 734 if (list_store->view && GTK_WIDGET_REALIZED(GTK_WIDGET(list_store->view))) { 735 HWND hWnd = GTK_WIDGET(list_store->view)->hWnd; 736 737 SendMessageW(hWnd, LB_DELETESTRING, (WPARAM)rowind, 0); 738 } 739 return TRUE; 740 } else { 741 return FALSE; 742 } 743 } 744 745 GtkWidget *gtk_scrolled_tree_view_new(GtkWidget **pack_widg) 746 { 747 GtkWidget *widget; 748 749 widget = gtk_tree_view_new(); 750 *pack_widg = widget; 751 return widget; 752 } 753 754 void gtk_tree_view_set_column_width(GtkTreeView *tv, gint column, gint width) 755 { 756 gtk_tree_view_set_column_width_full(tv, column, width, TRUE); 757 } 758 759 void gtk_tree_view_set_column_width_full(GtkTreeView *tv, gint column, 760 gint width, gboolean ResizeHeader) 761 { 762 int ncols; 763 GtkTreeViewColumn *col; 764 HWND hWnd, header; 765 HD_ITEM hdi; 766 767 ncols = g_slist_length(tv->columns); 768 if (column < 0 || column >= ncols) 769 return; 770 col = g_slist_nth_data(tv->columns, column); 771 772 col->width = width; 773 if (GTK_WIDGET_REALIZED(GTK_WIDGET(tv))) { 774 header = tv->header; 775 if (ResizeHeader && header) { 776 hdi.mask = HDI_WIDTH; 777 if (column == ncols - 1) 778 width = 9000; 779 hdi.cxy = width; 780 if (SendMessageW(header, HDM_GETITEM, (WPARAM)column, (LPARAM)&hdi) 781 && hdi.cxy != width) { 782 hdi.mask = HDI_WIDTH; 783 hdi.cxy = width; 784 SendMessageW(header, HDM_SETITEM, (WPARAM)column, (LPARAM)&hdi); 785 } 786 } 787 gtk_tree_view_set_extent(tv); 788 hWnd = GTK_WIDGET(tv)->hWnd; 789 if (hWnd) 790 InvalidateRect(hWnd, NULL, FALSE); 791 } 792 } 793 794 void gtk_tree_selection_set_mode(GtkTreeSelection *selection, 795 GtkSelectionMode type) 796 { 797 selection->mode = type; 798 } 799 800 void gtk_tree_selection_select_path(GtkTreeSelection *selection, 801 GtkTreePath *path) 802 { 803 HWND hWnd; 804 guint row = *path; 805 806 hWnd = GTK_WIDGET(selection)->hWnd; 807 if (hWnd) { 808 if (selection->mode == GTK_SELECTION_SINGLE) { 809 SendMessageW(hWnd, LB_SETCURSEL, (WPARAM)row, 0); 810 } else { 811 SendMessageW(hWnd, LB_SETSEL, (WPARAM)TRUE, (LPARAM)row); 812 } 813 gtk_tree_view_update_selection(GTK_WIDGET(selection)); 814 } 815 } 816 817 void gtk_tree_selection_unselect_all(GtkTreeSelection *selection) 818 { 819 GList *sel; 820 for (sel = selection->selection; sel; sel = g_list_next(sel)) { 821 guint row = GPOINTER_TO_UINT(sel->data); 822 gtk_tree_selection_unselect_path(selection, &row); 823 } 824 } 825 826 GList *gtk_tree_selection_get_selected_rows(GtkTreeSelection *selection, 827 GtkTreeModel **model) 828 { 829 GList *sel, *pathsel = NULL; 830 for (sel = selection->selection; sel; sel = g_list_next(sel)) { 831 guint row = GPOINTER_TO_UINT(sel->data); 832 GtkTreePath *path = g_new(GtkTreePath, 1); 833 *path = row; 834 pathsel = g_list_append(pathsel, path); 835 } 836 if (model) { 837 *model = selection->model; 838 } 839 return pathsel; 840 } 841 842 gint *gtk_tree_path_get_indices_with_depth(GtkTreePath *path, gint *depth) 843 { 844 /* Only one level; path *is* the row index */ 845 *depth = 1; 846 return (gint *)path; 847 } 848 849 void gtk_tree_selection_unselect_path(GtkTreeSelection *selection, 850 GtkTreePath *path) 851 { 852 HWND hWnd; 853 guint row = *path; 854 855 hWnd = GTK_WIDGET(selection)->hWnd; 856 if (hWnd) { 857 if (selection->mode == GTK_SELECTION_SINGLE) { 858 SendMessageW(hWnd, LB_SETCURSEL, (WPARAM)(-1), 0); 859 } else { 860 SendMessageW(hWnd, LB_SETSEL, (WPARAM)FALSE, (LPARAM)row); 861 } 862 gtk_tree_view_update_selection(GTK_WIDGET(selection)); 863 } 864 } 865 866 gint gtk_tree_selection_count_selected_rows(GtkTreeSelection *selection) 867 { 868 return g_list_length(selection->selection); 869 } 870 871 gboolean gtk_tree_selection_get_selected(GtkTreeSelection *selection, 872 GtkTreeModel **model, 873 GtkTreeIter *iter) 874 { 875 if (model) { 876 *model = selection->model; 877 } 878 879 /* Just return the first selected row */ 880 if (selection->selection) { 881 if (iter) { 882 int row = GPOINTER_TO_INT(g_list_nth_data(selection->selection, 0)); 883 *iter = row; 884 } 885 return TRUE; 886 } else { 887 return FALSE; 888 } 889 } 890 891 void gtk_tree_selection_selected_foreach(GtkTreeSelection *selection, 892 GtkTreeSelectionForeachFunc func, 893 gpointer data) 894 { 895 GList *sel; 896 for (sel = selection->selection; sel; sel = g_list_next(sel)) { 897 guint row = GPOINTER_TO_UINT(sel->data); 898 func(selection->model, &row, &row, data); 899 } 900 } 901 902 void gtk_tree_view_update_selection(GtkWidget *widget) 903 { 904 GtkTreeView *tv = GTK_TREE_VIEW(widget); 905 gint i; 906 907 g_list_free(tv->selection); 908 tv->selection = NULL; 909 if (widget->hWnd) { 910 if (tv->model) for (i = 0; i < tv->model->rows->len; i++) { 911 if (SendMessageW(widget->hWnd, LB_GETSEL, (WPARAM)i, 0) > 0) { 912 tv->selection = g_list_append(tv->selection, GINT_TO_POINTER(i)); 913 } 914 } 915 916 gtk_signal_emit(G_OBJECT(widget), "changed"); 917 } 918 } 919 920 static LRESULT CALLBACK TreeViewHdrWndProc(HWND hwnd, UINT msg, WPARAM wParam, 921 LPARAM lParam) 922 { 923 GtkWidget *widget; 924 gboolean retval = FALSE, dodef = TRUE; 925 926 widget = GTK_WIDGET(GetWindowLongPtr(hwnd, GWLP_USERDATA)); 927 928 if (widget) { 929 retval = gtk_tree_view_wndproc(widget, msg, wParam, lParam, &dodef); 930 } 931 932 if (dodef) { 933 return DefWindowProcW(hwnd, msg, wParam, lParam); 934 } else { 935 return retval; 936 } 937 } 938 939 void InitTreeViewClass(HINSTANCE hInstance) 940 { 941 WNDCLASS wc; 942 943 wc.style = 0; 944 wc.lpfnWndProc = TreeViewHdrWndProc; 945 wc.cbClsExtra = 0; 946 wc.cbWndExtra = 0; 947 wc.hInstance = hInstance; 948 wc.hIcon = NULL; 949 wc.hCursor = LoadCursor(NULL, IDC_ARROW); 950 wc.hbrBackground = NULL; 951 wc.lpszMenuName = NULL; 952 wc.lpszClassName = WC_GTKTREEVIEWHDR; 953 myRegisterClass(&wc); 954 } 955 956 /* Make a new GtkListStore and fill in the column types */ 957 GtkListStore *gtk_list_store_new(gint n_columns, ...) 958 { 959 GtkListStore *store; 960 int i; 961 962 va_list ap; 963 va_start(ap, n_columns); 964 965 store = GTK_LIST_STORE(GtkNewObject(&GtkListStoreClass)); 966 store->view = NULL; 967 store->ncols = n_columns; 968 store->coltype = g_new(int, n_columns); 969 store->rows = g_array_new(FALSE, FALSE, sizeof(GtkListStoreRow)); 970 store->sort_column_id = GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID; 971 store->sort_func = g_array_new(FALSE, TRUE, sizeof(gpointer)); 972 store->need_sort = FALSE; 973 for (i = 0; i < n_columns; ++i) { 974 store->coltype[i] = va_arg(ap, int); 975 } 976 va_end(ap); 977 return store; 978 } 979 980 void gtk_tree_sortable_set_sort_func(GtkTreeSortable *sortable, 981 gint sort_column_id, 982 GtkTreeIterCompareFunc sort_func, 983 gpointer user_data, 984 GDestroyNotify destroy) 985 { 986 /* We don't currently support user_data */ 987 if (sort_column_id >= sortable->sort_func->len) { 988 g_array_set_size(sortable->sort_func, sort_column_id+1); 989 } 990 g_array_index(sortable->sort_func, gpointer, sort_column_id) = sort_func; 991 } 992 993 void gtk_tree_sortable_set_sort_column_id(GtkTreeSortable *sortable, 994 gint sort_column_id, 995 GtkSortType order) 996 { 997 if (sortable->sort_column_id != sort_column_id 998 || sortable->sort_order != order) { 999 sortable->sort_column_id = sort_column_id; 1000 sortable->sort_order = order; 1001 sortable->need_sort = TRUE; 1002 } 1003 } 1004 1005 /* We don't support customizing renderers right now */ 1006 GtkCellRenderer *gtk_cell_renderer_text_new(void) 1007 { 1008 return NULL; 1009 } 1010 1011 static GtkTreeViewColumn *new_column_internal(const char *title, va_list args) 1012 { 1013 GtkTreeViewColumn *col; 1014 const char *name; 1015 1016 col = g_new0(GtkTreeViewColumn, 1); 1017 col->title = g_strdup(title); 1018 col->resizeable = FALSE; 1019 col->expand = FALSE; 1020 col->auto_resize = TRUE; 1021 col->sort_column_id = -1; 1022 col->model_column = -1; 1023 col->xalign = 0.0; /* left align by default */ 1024 1025 /* Currently we only support the "text" attribute to point to the 1026 ListStore column */ 1027 while ((name = va_arg(args, const char *)) != NULL) { 1028 if (strcmp(name, "text") == 0) { 1029 col->model_column = va_arg(args, int); 1030 } 1031 } 1032 return col; 1033 } 1034 1035 GtkTreeViewColumn *gtk_tree_view_column_new_with_attributes 1036 (const gchar *title, GtkCellRenderer *cell, ...) 1037 { 1038 GtkTreeViewColumn *col; 1039 va_list args; 1040 1041 va_start(args, cell); 1042 col = new_column_internal(title, args); 1043 va_end(args); 1044 return col; 1045 } 1046 1047 gint gtk_tree_view_insert_column_with_attributes 1048 (GtkTreeView *tree_view, gint position, const gchar *title, 1049 GtkCellRenderer *cell, ...) 1050 { 1051 GtkTreeViewColumn *col; 1052 va_list args; 1053 1054 va_start(args, cell); 1055 col = new_column_internal(title, args); 1056 va_end(args); 1057 return gtk_tree_view_insert_column(tree_view, col, position); 1058 } 1059 1060 void gtk_tree_view_scroll_to_cell(GtkTreeView *tree_view, 1061 GtkTreePath *path, 1062 GtkTreeViewColumn *column, 1063 gboolean use_align, gfloat row_align, 1064 gfloat col_align) 1065 { 1066 /* not implemented */ 1067 } 1068 1069 void gtk_tree_view_column_set_resizable(GtkTreeViewColumn *tree_column, 1070 gboolean resizable) 1071 { 1072 tree_column->resizeable = resizable; 1073 } 1074 1075 void gtk_tree_view_column_set_expand(GtkTreeViewColumn *tree_column, 1076 gboolean expand) 1077 { 1078 tree_column->expand = expand; 1079 } 1080 1081 void gtk_tree_view_column_set_sort_column_id(GtkTreeViewColumn *tree_column, 1082 gint sort_column_id) 1083 { 1084 tree_column->sort_column_id = sort_column_id; 1085 } 1086 1087 void gtk_tree_view_column_set_alignment(GtkTreeViewColumn *tree_column, 1088 gfloat xalign) 1089 { 1090 tree_column->xalign = xalign; 1091 } 1092 1093 gint gtk_tree_view_insert_column(GtkTreeView *tree_view, 1094 GtkTreeViewColumn *column, 1095 gint position) 1096 { 1097 tree_view->columns = g_slist_insert(tree_view->columns, column, position); 1098 return g_slist_length(tree_view->columns); 1099 } 1100 1101 GtkTreeViewColumn *gtk_tree_view_get_column(GtkTreeView *tree_view, gint n) 1102 { 1103 return g_slist_nth_data(tree_view->columns, n); 1104 } 1105 1106 void gtk_tree_view_set_model(GtkTreeView *tree_view, GtkTreeModel *model) 1107 { 1108 /* We only support a single model per view, so ignore attempts to remove it */ 1109 if (model) { 1110 tree_view->model = model; 1111 model->view = tree_view; 1112 } 1113 } 1114 1115 struct ListStoreSortData { 1116 GtkTreeIterCompareFunc sort_func; 1117 GtkListStore *store; 1118 gboolean reversed; 1119 }; 1120 1121 static gint tree_view_sort_func(gconstpointer a, gconstpointer b, gpointer data){ 1122 /* Map from sorting an array of guint indices into sorting a GtkListStore */ 1123 struct ListStoreSortData *d = data; 1124 const guint *inda = a, *indb = b; 1125 if (d->reversed) { 1126 return d->sort_func(d->store, (guint*)indb, (guint*)inda, NULL); 1127 } else { 1128 return d->sort_func(d->store, (guint*)inda, (guint*)indb, NULL); 1129 } 1130 } 1131 1132 void gtk_tree_view_sort(GtkTreeView *tv) 1133 { 1134 GtkListStore *model = tv->model; 1135 struct ListStoreSortData data; 1136 HWND hWnd; 1137 GArray *inds, *revinds; 1138 guint i; 1139 GArray *newrows; 1140 if (!model || !model->need_sort 1141 || model->sort_column_id == GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID) { 1142 return; 1143 } 1144 1145 /* Before sort, inds[row] = row */ 1146 inds = g_array_sized_new(FALSE, FALSE, sizeof(guint), model->rows->len); 1147 for (i = 0; i < model->rows->len; ++i) { 1148 g_array_append_val(inds, i); 1149 } 1150 data.sort_func = g_array_index(model->sort_func, GtkTreeIterCompareFunc, 1151 model->sort_column_id); 1152 data.store = model; 1153 data.reversed = model->sort_order == GTK_SORT_DESCENDING; 1154 g_array_sort_with_data(inds, tree_view_sort_func, &data); 1155 1156 /* After sort, inds[newrow] = oldrow */ 1157 /* Now we can reconstruct model->rows in the new order */ 1158 newrows = g_array_sized_new(FALSE, FALSE, sizeof(GtkListStoreRow), 1159 model->rows->len); 1160 for (i = 0; i < model->rows->len; ++i) { 1161 guint oldrow = g_array_index(inds, guint, i); 1162 g_array_append_val(newrows, 1163 g_array_index(model->rows, GtkListStoreRow, oldrow)); 1164 } 1165 1166 /* Make revinds[oldrow] = newrow */ 1167 revinds = g_array_sized_new(FALSE, FALSE, sizeof(guint), model->rows->len); 1168 g_array_set_size(revinds, model->rows->len); 1169 for (i = 0; i < model->rows->len; ++i) { 1170 guint oldrow = g_array_index(inds, guint, i); 1171 g_array_index(revinds, guint, oldrow) = i; 1172 } 1173 1174 /* Update selection (only works for a single selection currently) */ 1175 if (tv->selection) { 1176 guint oldrow = GPOINTER_TO_UINT(tv->selection->data); 1177 guint newrow = g_array_index(revinds, guint, oldrow); 1178 if (oldrow != newrow) { 1179 gtk_tree_selection_unselect_path(tv, &oldrow); 1180 gtk_tree_selection_select_path(tv, &newrow); 1181 } 1182 } 1183 1184 /* No need to free the old row data since the new structure takes ownership */ 1185 g_array_free(model->rows, TRUE); 1186 model->rows = newrows; 1187 1188 g_array_free(inds, TRUE); 1189 g_array_free(revinds, TRUE); 1190 model->need_sort = FALSE; 1191 1192 hWnd = GTK_WIDGET(tv)->hWnd; 1193 if (hWnd) 1194 InvalidateRect(hWnd, NULL, FALSE); 1195 } 1196 1197 GtkTreeModel *gtk_tree_view_get_model(GtkTreeView *tree_view) 1198 { 1199 return tree_view->model; 1200 } 1201 1202 void gtk_tree_view_set_headers_clickable(GtkTreeView *tree_view, 1203 gboolean setting) 1204 { 1205 tree_view->headers_clickable = setting; 1206 } 1207 1208 /* These are noops; we only use these for GtkListStore, which should always 1209 be owned (and thus freed) by our GtkTreeView */ 1210 void g_object_unref(gpointer object) 1211 { 1212 } 1213 1214 gpointer g_object_ref(gpointer object) 1215 { 1216 return object; 1217 } 1218 1219 #else /* for systems with GTK+ */ 1220 1221 GtkWidget *gtk_scrolled_tree_view_new(GtkWidget **pack_widg) 1222 { 1223 GtkWidget *scrollwin, *clist; 1224 1225 clist = gtk_tree_view_new(); 1226 scrollwin = gtk_scrolled_window_new(NULL, NULL); 1227 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin), 1228 GTK_POLICY_AUTOMATIC, 1229 GTK_POLICY_AUTOMATIC); 1230 gtk_container_add(GTK_CONTAINER(scrollwin), clist); 1231 *pack_widg = scrollwin; 1232 return clist; 1233 } 1234 1235 #endif