src/mn-text-table.gob (7870B) - raw
1 /* 2 * Mail Notification 3 * Copyright (C) 2003-2008 Jean-Yves Lefort <jylefort@brutele.be> 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 */ 19 20 %headertop{ 21 #include <gtk/gtk.h> 22 %} 23 24 %h{ 25 typedef struct _MNTextTableCell MNTextTableCell; 26 %} 27 28 %privateheader{ 29 typedef struct 30 { 31 GPtrArray *cells; 32 int height; 33 } Row; 34 %} 35 36 %{ 37 #include "mn-util.h" 38 39 #define HORIZONTAL_SPACING 12 40 #define VERTICAL_SPACING 0 41 42 #define COLUMN_SIZE(self, column) \ 43 g_array_index((self)->_priv->column_sizes, int, (column)) 44 45 struct _MNTextTableCell 46 { 47 PangoLayout *layout; /* NULL for a blank cell */ 48 gboolean dirty; 49 int width; 50 int height; 51 }; 52 %} 53 54 /* 55 * This widget is used to display the mail summaries. 56 * 57 * We do not use a GtkBox (or GtkTable) containing labels because it 58 * is extremely slow when we add a few hundred labels. 59 * 60 * A possible alternative would be to use a GtkTextView, which 61 * provides acceptable performance. However, creating a table using a 62 * GtkTextView is extremely cumbersome. 63 */ 64 65 class MN:Text:Table from Gtk:Widget 66 { 67 private GPtrArray *rows = {g_ptr_array_new()} 68 destroy { mn_g_ptr_array_free_deep_custom(VAR, (GFunc) self_row_free, NULL); }; 69 70 private GArray *column_sizes = {g_array_new(FALSE, TRUE, sizeof(int))} 71 destroy { g_array_free(VAR, TRUE); }; 72 73 private Row *row; 74 75 private gboolean dirty = TRUE; 76 private int width; 77 private int height; 78 79 init (self) 80 { 81 GTK_WIDGET_SET_FLAGS(self, GTK_NO_WINDOW); 82 83 g_object_connect(self, 84 "swapped-signal::style-set", self_context_changed, self, 85 "swapped-signal::direction-changed", self_context_changed, self, 86 NULL); 87 } 88 89 private void 90 row_free (Row *row (check null)) 91 { 92 mn_g_ptr_array_free_deep_custom(row->cells, (GFunc) self_cell_free, NULL); 93 g_free(row); 94 } 95 96 private void 97 cell_free (MNTextTableCell *cell (check null)) 98 { 99 if (cell->layout) 100 g_object_unref(cell->layout); 101 g_free(cell); 102 } 103 104 override (Gtk:Widget) void 105 size_request (GtkWidget *widget, GtkRequisition *requisition) 106 { 107 Self *self = SELF(widget); 108 109 self_relayout(self); 110 111 requisition->width = selfp->width; 112 requisition->height = selfp->height; 113 } 114 115 override (Gtk:Widget) gboolean 116 expose_event (GtkWidget *widget, GdkEventExpose *event) 117 { 118 Self *self = SELF(widget); 119 int i; 120 int y = widget->allocation.y; 121 122 if (! GTK_WIDGET_DRAWABLE(widget)) 123 return FALSE; 124 125 self_relayout(self); 126 127 MN_ARRAY_FOREACH(i, selfp->rows) 128 { 129 Row *row = g_ptr_array_index(selfp->rows, i); 130 int j; 131 int x = widget->allocation.x; 132 int column = 0; 133 134 MN_ARRAY_FOREACH(j, row->cells) 135 { 136 MNTextTableCell *cell = g_ptr_array_index(row->cells, j); 137 138 if (cell->layout) 139 gtk_paint_layout(widget->style, 140 widget->window, 141 GTK_WIDGET_STATE(widget), 142 FALSE, 143 &event->area, 144 widget, 145 NULL, 146 x, 147 y + row->height - cell->height, 148 cell->layout); 149 150 x += COLUMN_SIZE(self, column); 151 if (j < row->cells->len - 1) 152 x += HORIZONTAL_SPACING; 153 154 column++; 155 } 156 157 y += row->height; 158 if (i < selfp->rows->len - 1) 159 y += VERTICAL_SPACING; 160 } 161 162 return FALSE; 163 } 164 165 private void 166 set_dirty (self) 167 { 168 if (! selfp->dirty) 169 { 170 selfp->dirty = TRUE; 171 gtk_widget_queue_resize(GTK_WIDGET(self)); 172 } 173 } 174 175 private void 176 context_changed (self) 177 { 178 int i; 179 180 MN_ARRAY_FOREACH(i, selfp->rows) 181 { 182 Row *row = g_ptr_array_index(selfp->rows, i); 183 int j; 184 185 MN_ARRAY_FOREACH(j, row->cells) 186 { 187 MNTextTableCell *cell = g_ptr_array_index(row->cells, j); 188 189 if (cell->layout) 190 { 191 pango_layout_context_changed(cell->layout); 192 cell->dirty = TRUE; 193 } 194 } 195 } 196 197 self_set_dirty(self); 198 } 199 200 private void 201 relayout (self) 202 { 203 int i; 204 205 if (! selfp->dirty) 206 return; 207 208 selfp->width = 0; 209 selfp->height = 0; 210 211 MN_ARRAY_FOREACH(i, selfp->column_sizes) 212 COLUMN_SIZE(self, i) = 0; 213 214 MN_ARRAY_FOREACH(i, selfp->rows) 215 { 216 Row *row = g_ptr_array_index(selfp->rows, i); 217 int j; 218 219 row->height = 0; 220 221 MN_ARRAY_FOREACH(j, row->cells) 222 { 223 MNTextTableCell *cell = g_ptr_array_index(row->cells, j); 224 int n_columns = j + 1; 225 226 if (cell->dirty) 227 { 228 g_assert(cell->layout != NULL); 229 230 pango_layout_get_pixel_size(cell->layout, &cell->width, &cell->height); 231 cell->dirty = FALSE; 232 } 233 234 if (n_columns > selfp->column_sizes->len) 235 g_array_set_size(selfp->column_sizes, n_columns); 236 237 if (cell->width > COLUMN_SIZE(self, j)) 238 COLUMN_SIZE(self, j) = cell->width; 239 if (cell->height > row->height) 240 row->height = cell->height; 241 } 242 243 selfp->height += row->height; 244 } 245 246 MN_ARRAY_FOREACH(i, selfp->column_sizes) 247 selfp->width += COLUMN_SIZE(self, i); 248 249 if (selfp->column_sizes->len > 1) 250 selfp->width += HORIZONTAL_SPACING * (selfp->column_sizes->len - 1); 251 if (selfp->rows->len > 1) 252 selfp->height += VERTICAL_SPACING * (selfp->rows->len - 1); 253 254 selfp->dirty = FALSE; 255 } 256 257 virtual public void 258 clear (self) 259 { 260 mn_g_ptr_array_free_deep_custom(selfp->rows, (GFunc) self_row_free, NULL); 261 selfp->rows = g_ptr_array_new(); 262 263 g_array_set_size(selfp->column_sizes, 0); 264 265 selfp->row = NULL; 266 267 self_set_dirty(self); 268 } 269 270 public MNTextTableCell * 271 append_text_cell (self, const char *text) 272 { 273 return self_append_text_cell_from_layout(self, gtk_widget_create_pango_layout(GTK_WIDGET(self), text)); 274 } 275 276 public MNTextTableCell * 277 append_text_cell_from_markup (self, const char *markup (check null)) 278 { 279 PangoLayout *layout; 280 281 layout = gtk_widget_create_pango_layout(GTK_WIDGET(self), NULL); 282 pango_layout_set_markup(layout, markup, -1); 283 284 return self_append_text_cell_from_layout(self, layout); 285 } 286 287 public MNTextTableCell * 288 append_text_cell_from_layout (self, Pango:Layout *layout (check null)) 289 { 290 MNTextTableCell *cell; 291 292 cell = g_new(MNTextTableCell, 1); 293 cell->layout = layout; 294 cell->dirty = TRUE; 295 296 self_append_cell_real(self, cell); 297 298 return cell; 299 } 300 301 public MNTextTableCell * 302 append_blank_cell (self, int width, int height) 303 { 304 MNTextTableCell *cell; 305 306 cell = g_new(MNTextTableCell, 1); 307 cell->layout = NULL; 308 cell->dirty = FALSE; 309 cell->width = width; 310 cell->height = height; 311 312 self_append_cell_real(self, cell); 313 314 return cell; 315 } 316 317 private void 318 append_cell_real (self, MNTextTableCell *cell) 319 { 320 if (! selfp->row) 321 { 322 selfp->row = g_new(Row, 1); 323 selfp->row->cells = g_ptr_array_new(); 324 325 g_ptr_array_add(selfp->rows, selfp->row); 326 } 327 328 g_ptr_array_add(selfp->row->cells, cell); 329 330 self_set_dirty(self); 331 } 332 333 public void 334 cell_set_text (self, 335 MNTextTableCell *cell (check null), 336 const char *text (check null)) 337 { 338 const char *current_text; 339 340 g_return_if_fail(cell->layout != NULL); 341 342 current_text = pango_layout_get_text(cell->layout); 343 if (! current_text || strcmp(current_text, text)) 344 { 345 pango_layout_set_text(cell->layout, text, -1); 346 cell->dirty = TRUE; 347 self_set_dirty(self); 348 } 349 } 350 351 public void 352 line_break (self) 353 { 354 selfp->row = NULL; 355 } 356 }