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 }