]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * This file is part of the sigrok project. | |
3 | * | |
4 | * Copyright (C) 2011 Gareth McMullin <gareth@blacksphere.co.nz> | |
5 | * | |
6 | * This program is free software: you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation, either version 3 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
18 | */ | |
19 | ||
20 | #include <sigrok.h> | |
21 | #include <gtk/gtk.h> | |
22 | ||
23 | #include <string.h> | |
24 | #include <math.h> | |
25 | ||
26 | #include "gtkcellrenderersignal.h" | |
27 | #include "sigrok-gtk.h" | |
28 | ||
29 | /* FIXME: No globals */ | |
30 | GtkListStore *siglist; | |
31 | ||
32 | static void format_func(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, | |
33 | GtkTreeModel *siglist, GtkTreeIter *iter, gpointer cb_data) | |
34 | { | |
35 | int probe; | |
36 | char *colour; | |
37 | GArray *data; | |
38 | ||
39 | (void)tree_column; | |
40 | (void)cb_data; | |
41 | ||
42 | /* TODO: In future we will get data here from tree model. | |
43 | * PD data streams will be kept in the list. The logic stream | |
44 | * will be used if data is NULL. | |
45 | */ | |
46 | gtk_tree_model_get(siglist, iter, 1, &colour, 2, &probe, -1); | |
47 | ||
48 | /* Try get summary data from the list */ | |
49 | data = g_object_get_data(G_OBJECT(siglist), "summarydata"); | |
50 | if (!data) | |
51 | data = g_object_get_data(G_OBJECT(siglist), "sampledata"); | |
52 | ||
53 | g_object_set(G_OBJECT(cell), "data", data, "probe", probe, | |
54 | "foreground", colour, NULL); | |
55 | } | |
56 | ||
57 | static gboolean do_scroll_event(GtkTreeView *tv, GdkEventScroll *e) | |
58 | { | |
59 | gint x, y, cx; | |
60 | GtkTreeViewColumn *col; | |
61 | ||
62 | gtk_tree_view_widget_to_tree_coords(tv, e->x, e->y, &x, &y); | |
63 | if (!gtk_tree_view_get_path_at_pos(tv, x, y, NULL, &col, &cx, NULL)) | |
64 | return FALSE; | |
65 | if (col != g_object_get_data(G_OBJECT(tv), "signalcol")) | |
66 | return FALSE; | |
67 | ||
68 | switch (e->direction) { | |
69 | case GDK_SCROLL_UP: | |
70 | sigview_zoom(GTK_WIDGET(tv), 1.2, cx); | |
71 | break; | |
72 | case GDK_SCROLL_DOWN: | |
73 | sigview_zoom(GTK_WIDGET(tv), 1/1.2, cx); | |
74 | break; | |
75 | default: | |
76 | /* Surpress warning about unswitch enum values */ | |
77 | break; | |
78 | } | |
79 | ||
80 | return TRUE; | |
81 | } | |
82 | ||
83 | static gboolean do_motion_event(GtkWidget *tv, GdkEventMotion *e, | |
84 | GObject *cel) | |
85 | { | |
86 | GObject *siglist; | |
87 | gint x; | |
88 | gint offset; | |
89 | GArray *data; | |
90 | guint nsamples; | |
91 | GtkTreeViewColumn *col; | |
92 | gint width; | |
93 | gdouble scale, *rscale; | |
94 | GtkAdjustment *adj; | |
95 | ||
96 | x = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(tv), "motion-x")); | |
97 | g_object_set_data(G_OBJECT(tv), "motion-x", GINT_TO_POINTER((gint)e->x)); | |
98 | adj = g_object_get_data(G_OBJECT(tv), "hadj"); | |
99 | ||
100 | siglist = G_OBJECT(gtk_tree_view_get_model(GTK_TREE_VIEW(tv))); | |
101 | data = g_object_get_data(siglist, "sampledata"); | |
102 | rscale = g_object_get_data(siglist, "rscale"); | |
103 | nsamples = (data->len / g_array_get_element_size(data)) - 1; | |
104 | col = g_object_get_data(G_OBJECT(tv), "signalcol"); | |
105 | width = gtk_tree_view_column_get_width(col); | |
106 | ||
107 | g_object_get(cel, "offset", &offset, "scale", &scale, NULL); | |
108 | offset += x - e->x; | |
109 | if (offset < 0) | |
110 | offset = 0; | |
111 | if (offset > nsamples * *rscale - width) | |
112 | offset = nsamples * *rscale - width; | |
113 | ||
114 | gtk_adjustment_set_value(adj, offset); | |
115 | ||
116 | return TRUE; | |
117 | } | |
118 | ||
119 | static gboolean do_button_event(GtkTreeView *tv, GdkEventButton *e, | |
120 | GObject *cel) | |
121 | { | |
122 | int h; | |
123 | gint x, y; | |
124 | GtkTreeViewColumn *col; | |
125 | ||
126 | if (e->button != 3) | |
127 | return FALSE; | |
128 | ||
129 | gtk_tree_view_widget_to_tree_coords(tv, e->x, e->y, &x, &y); | |
130 | ||
131 | switch (e->type) { | |
132 | case GDK_BUTTON_PRESS: | |
133 | if (!gtk_tree_view_get_path_at_pos(tv, x, y, NULL, &col, NULL, NULL)) | |
134 | return FALSE; | |
135 | if (col != g_object_get_data(G_OBJECT(tv), "signalcol")) | |
136 | return FALSE; | |
137 | h = g_signal_connect(tv, "motion-notify-event", G_CALLBACK(do_motion_event), cel); | |
138 | g_object_set_data(G_OBJECT(tv), "motion-handler", GINT_TO_POINTER(h)); | |
139 | g_object_set_data(G_OBJECT(tv), "motion-x", GINT_TO_POINTER((gint)e->x)); | |
140 | break; | |
141 | case GDK_BUTTON_RELEASE: | |
142 | h = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(tv), "motion-handler")); | |
143 | if (!h) | |
144 | return TRUE; | |
145 | g_signal_handler_disconnect(GTK_WIDGET(tv), h); | |
146 | g_object_set_data(G_OBJECT(tv), "motion-handler", NULL); | |
147 | break; | |
148 | default: | |
149 | /* Surpress warning about unswitch enum values */ | |
150 | break; | |
151 | } | |
152 | return TRUE; | |
153 | } | |
154 | ||
155 | static void col_resized(GtkWidget *col) | |
156 | { | |
157 | sigview_zoom(col, 1, 0); | |
158 | } | |
159 | ||
160 | static void pan_changed(GtkAdjustment *adj, GtkWidget *sigview) | |
161 | { | |
162 | GObject *cel = g_object_get_data(G_OBJECT(sigview), "signalcel"); | |
163 | g_object_set(cel, "offset", (gint)gtk_adjustment_get_value(adj), NULL); | |
164 | gtk_widget_queue_draw(sigview); | |
165 | } | |
166 | ||
167 | GtkWidget *sigview_init(void) | |
168 | { | |
169 | GtkWidget *sw, *tv; | |
170 | GtkTreeViewColumn *col; | |
171 | GtkCellRenderer *cel; | |
172 | ||
173 | sw = gtk_scrolled_window_new(NULL, NULL); | |
174 | gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), | |
175 | GTK_POLICY_ALWAYS, GTK_POLICY_AUTOMATIC); | |
176 | tv = gtk_tree_view_new(); | |
177 | gtk_container_add(GTK_CONTAINER(sw), tv); | |
178 | gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tv), FALSE); | |
179 | g_object_set(G_OBJECT(tv), "reorderable", TRUE, NULL); | |
180 | ||
181 | col = gtk_tree_view_column_new_with_attributes(NULL, | |
182 | gtk_cell_renderer_text_new(), | |
183 | "text", 0, "foreground", 1, NULL); | |
184 | gtk_tree_view_append_column(GTK_TREE_VIEW(tv), col); | |
185 | ||
186 | cel = gtk_cell_renderer_signal_new(); | |
187 | g_object_set(G_OBJECT(cel), "ypad", 1, NULL); | |
188 | g_signal_connect(tv, "scroll-event", G_CALLBACK(do_scroll_event), cel); | |
189 | g_signal_connect(tv, "button-press-event", G_CALLBACK(do_button_event), cel); | |
190 | g_signal_connect(tv, "button-release-event", G_CALLBACK(do_button_event), cel); | |
191 | col = gtk_tree_view_column_new(); | |
192 | g_object_set_data(G_OBJECT(tv), "signalcol", col); | |
193 | g_object_set_data(G_OBJECT(tv), "signalcel", cel); | |
194 | gtk_tree_view_column_pack_start(col, cel, TRUE); | |
195 | gtk_tree_view_column_set_cell_data_func(col, cel, format_func, | |
196 | NULL, NULL); | |
197 | gtk_tree_view_append_column(GTK_TREE_VIEW(tv), col); | |
198 | g_signal_connect_swapped(col, "notify::width", | |
199 | G_CALLBACK(col_resized), tv); | |
200 | ||
201 | siglist = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_STRING, | |
202 | G_TYPE_INT); | |
203 | gtk_tree_view_set_model(GTK_TREE_VIEW(tv), GTK_TREE_MODEL(siglist)); | |
204 | ||
205 | GtkObject *pan = gtk_adjustment_new(0, 0, 0, 1, 1, 1); | |
206 | g_object_set_data(G_OBJECT(tv), "hadj", pan); | |
207 | gtk_range_set_adjustment(GTK_RANGE(GTK_SCROLLED_WINDOW(sw)->hscrollbar), | |
208 | GTK_ADJUSTMENT(pan)); | |
209 | g_signal_connect(pan, "value-changed", G_CALLBACK(pan_changed), tv); | |
210 | ||
211 | return sw; | |
212 | } | |
213 | ||
214 | static GArray *summarize(GArray *in, gdouble *scale) | |
215 | { | |
216 | GArray *ret; | |
217 | int skip = 1 / (*scale * 4); | |
218 | guint64 i, j, k, l; | |
219 | unsigned unitsize = g_array_get_element_size(in); | |
220 | uint8_t s[unitsize]; | |
221 | uint8_t smask[unitsize]; | |
222 | ||
223 | g_return_val_if_fail(skip > 1, NULL); | |
224 | ||
225 | ret = g_array_sized_new(FALSE, FALSE, unitsize, in->len / skip); | |
226 | ret->len = in->len / skip; | |
227 | *scale *= skip; | |
228 | ||
229 | memset(s, 0, unitsize); | |
230 | for (i = 0, k = 0; i < (in->len/unitsize); i += skip, k++) { | |
231 | memset(smask, 0xFF, unitsize); | |
232 | for (j = i; j < i+skip; j++) { | |
233 | for (l = 0; l < unitsize; l++) { | |
234 | uint8_t ns = (in->data[(j*unitsize)+l] ^ s[l]) & smask[l]; | |
235 | /* ns is now bits we need to toggle */ | |
236 | s[l] ^= ns; | |
237 | smask[l] &= ~ns; | |
238 | } | |
239 | } | |
240 | memcpy(&ret->data[k*unitsize], s, unitsize); | |
241 | } | |
242 | return ret; | |
243 | } | |
244 | ||
245 | void sigview_zoom(GtkWidget *sigview, gdouble zoom, gint offset) | |
246 | { | |
247 | GObject *siglist; | |
248 | GtkTreeViewColumn *col; | |
249 | GtkCellRendererSignal *cel; | |
250 | GtkAdjustment *adj; | |
251 | /* data and scale refer to summary */ | |
252 | GArray *data; | |
253 | gdouble scale; | |
254 | /* rdata and rscale refer to complete data */ | |
255 | GArray *rdata; | |
256 | gdouble *rscale; | |
257 | gint ofs; | |
258 | gint width; | |
259 | guint nsamples; | |
260 | ||
261 | /* This is so that sigview_zoom() may be called with pointer | |
262 | * to the GtkTreeView or containing GtkScrolledWindow, as is the | |
263 | * case when called from outside this module. | |
264 | */ | |
265 | if (GTK_IS_SCROLLED_WINDOW(sigview)) | |
266 | sigview = gtk_bin_get_child(GTK_BIN(sigview)); | |
267 | ||
268 | g_return_if_fail(GTK_IS_TREE_VIEW(sigview)); | |
269 | ||
270 | col = g_object_get_data(G_OBJECT(sigview), "signalcol"); | |
271 | adj = g_object_get_data(G_OBJECT(sigview), "hadj"); | |
272 | width = gtk_tree_view_column_get_width(col); | |
273 | ||
274 | siglist = G_OBJECT(gtk_tree_view_get_model(GTK_TREE_VIEW(sigview))); | |
275 | rdata = g_object_get_data(siglist, "sampledata"); | |
276 | rscale = g_object_get_data(siglist, "rscale"); | |
277 | if (!rscale) { | |
278 | rscale = g_malloc(sizeof(rscale)); | |
279 | *rscale = 1; | |
280 | g_object_set_data(siglist, "rscale", rscale); | |
281 | } | |
282 | data = g_object_get_data(siglist, "summarydata"); | |
283 | if (!rdata) | |
284 | return; | |
285 | if (!data) | |
286 | data = rdata; | |
287 | nsamples = (rdata->len / g_array_get_element_size(rdata)) - 1; | |
288 | if ((fabs(*rscale - (double)width/nsamples) < 1e-12) && (zoom < 1)) | |
289 | return; | |
290 | ||
291 | cel = g_object_get_data(G_OBJECT(sigview), "signalcel"); | |
292 | g_object_get(cel, "scale", &scale, "offset", &ofs, NULL); | |
293 | ||
294 | ofs += offset; | |
295 | ||
296 | scale *= zoom; | |
297 | *rscale *= zoom; | |
298 | ofs *= zoom; | |
299 | ||
300 | ofs -= offset; | |
301 | ||
302 | if (ofs < 0) | |
303 | ofs = 0; | |
304 | ||
305 | if (*rscale < (double)width/nsamples) { | |
306 | *rscale = (double)width/nsamples; | |
307 | scale = *rscale; | |
308 | if (data && (data != rdata)) | |
309 | g_array_free(data, TRUE); | |
310 | data = rdata; | |
311 | } | |
312 | ||
313 | if (ofs > nsamples * *rscale - width) | |
314 | ofs = nsamples * *rscale - width; | |
315 | ||
316 | gtk_adjustment_configure(adj, ofs, 0, nsamples * *rscale, | |
317 | width/16, width/2, width); | |
318 | ||
319 | if (scale < 0.125) { | |
320 | g_object_set_data(siglist, | |
321 | "summarydata", summarize(data, &scale)); | |
322 | if (data && (data != rdata)) | |
323 | g_array_free(data, TRUE); | |
324 | } else if ((scale > 1) && (*rscale < 1)) { | |
325 | scale = *rscale; | |
326 | g_object_set_data(siglist, | |
327 | "summarydata", summarize(rdata, &scale)); | |
328 | if (data && (data != rdata)) | |
329 | g_array_free(data, TRUE); | |
330 | } | |
331 | ||
332 | g_object_set(cel, "scale", scale, "offset", ofs, NULL); | |
333 | gtk_widget_queue_draw(GTK_WIDGET(sigview)); | |
334 | } | |
335 |