ttreeview.c - vaccinewars - be a doctor and try to vaccinate the world
HTML git clone git://src.adamsgaard.dk/vaccinewars
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
ttreeview.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