1 /*
2 * KCemu -- The emulator for the KC85 homecomputer series and much more.
3 * Copyright (C) 1997-2010 Torsten Paul
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 2 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 #include <stdio.h>
21 #include <stdlib.h>
22
23 #include "kc/system.h"
24
25 #include "cmd/cmd.h"
26
27 #include "kc/kc.h"
28 #include "kc/memory.h"
29
30 #include "ui/gtk/savemem.h"
31 #include "ui/gtk/cmd.h"
32
33 #include "libdbg/dbg.h"
34
35 class CMD_ui_save_memory_window_toggle : public CMD
36 {
37 private:
38 SaveMemoryWindow *_w;
39
40 public:
CMD_ui_save_memory_window_toggle(SaveMemoryWindow * w)41 CMD_ui_save_memory_window_toggle(SaveMemoryWindow *w) : CMD("ui-save-memory-window-toggle")
42 {
43 _w = w;
44 register_cmd("ui-save-memory-window-toggle");
45 }
46
execute(CMD_Args * args,CMD_Context context)47 void execute(CMD_Args *args, CMD_Context context)
48 {
49 _w->toggle();
50 }
51 };
52
SaveMemoryWindow(const char * ui_xml_file)53 SaveMemoryWindow::SaveMemoryWindow(const char *ui_xml_file) : UI_Gtk_Window(ui_xml_file)
54 {
55 init();
56
57 _cmd_window_toggle = new CMD_ui_save_memory_window_toggle(this);
58 }
59
~SaveMemoryWindow(void)60 SaveMemoryWindow::~SaveMemoryWindow(void)
61 {
62 delete _cmd_window_toggle;
63 }
64
65 gboolean
on_output(GtkSpinButton * spin_button,gpointer user_data)66 SaveMemoryWindow::on_output(GtkSpinButton *spin_button, gpointer user_data)
67 {
68 GtkAdjustment *adj = gtk_spin_button_get_adjustment(spin_button);
69 int value = (int)gtk_adjustment_get_value(adj);
70 gchar *text = g_strdup_printf("%04X", value);
71 gtk_entry_set_text(GTK_ENTRY(spin_button), text);
72 g_free(text);
73
74 return TRUE;
75 }
76
77 gboolean
on_input(GtkSpinButton * spin_button,gdouble * new_value,gpointer user_data)78 SaveMemoryWindow::on_input(GtkSpinButton *spin_button, gdouble *new_value, gpointer user_data)
79 {
80 const gchar *text = gtk_entry_get_text(GTK_ENTRY(spin_button));
81
82 gchar *err = NULL;
83 unsigned long value = strtoul(text, &err, 16);
84 if (*err)
85 return GTK_INPUT_ERROR;
86
87 *new_value = (gdouble)value;
88 return TRUE;
89 }
90
91 void
on_adjustment_start_value_changed(GtkAdjustment * adjustment,gpointer user_data)92 SaveMemoryWindow::on_adjustment_start_value_changed(GtkAdjustment *adjustment, gpointer user_data)
93 {
94 SaveMemoryWindow *self = (SaveMemoryWindow *)user_data;
95
96 gdouble value_start = gtk_adjustment_get_value(adjustment);
97 gdouble value_end = gtk_adjustment_get_value(self->_w.adjustment_end);
98
99 if (value_end < value_start)
100 {
101 value_end = value_start;
102 self->set_adjustment_value_with_blocked_handler(self->_w.adjustment_end, value_end, self->_w.on_adjustment_end_value_changed_id);
103 }
104
105 self->set_length_adjustment();
106 self->apply_selection((int)gtk_adjustment_get_value(self->_w.adjustment_start), (int)gtk_adjustment_get_value(self->_w.adjustment_end));
107
108 on_jump_to_start(self->_w.toggle_button_jump_to_start, self);
109 }
110
111 void
on_adjustment_end_value_changed(GtkAdjustment * adjustment,gpointer user_data)112 SaveMemoryWindow::on_adjustment_end_value_changed(GtkAdjustment *adjustment, gpointer user_data)
113 {
114 SaveMemoryWindow *self = (SaveMemoryWindow *)user_data;
115
116 gdouble value_start = gtk_adjustment_get_value(self->_w.adjustment_start);
117 gdouble value_end = gtk_adjustment_get_value(adjustment);
118
119 if (value_end < value_start)
120 {
121 value_start = value_end;
122 self->set_adjustment_value_with_blocked_handler(self->_w.adjustment_start, value_start, self->_w.on_adjustment_start_value_changed_id);
123 }
124
125 self->set_length_adjustment();
126 self->apply_selection((int)gtk_adjustment_get_value(self->_w.adjustment_start), (int)gtk_adjustment_get_value(self->_w.adjustment_end));
127
128 on_jump_to_end(self->_w.toggle_button_jump_to_end, self);
129 }
130
131 void
on_adjustment_length_value_changed(GtkAdjustment * adjustment,gpointer user_data)132 SaveMemoryWindow::on_adjustment_length_value_changed(GtkAdjustment *adjustment, gpointer user_data)
133 {
134 SaveMemoryWindow *self = (SaveMemoryWindow *)user_data;
135
136 gdouble value_start = gtk_adjustment_get_value(self->_w.adjustment_start);
137 gdouble value_length = gtk_adjustment_get_value(adjustment);
138
139 gdouble value_end = value_start + value_length - 1;
140 self->set_adjustment_value_with_blocked_handler(self->_w.adjustment_end, value_end, self->_w.on_adjustment_end_value_changed_id);
141 self->apply_selection((int)gtk_adjustment_get_value(self->_w.adjustment_start), (int)gtk_adjustment_get_value(self->_w.adjustment_end));
142
143 on_jump_to_end(self->_w.toggle_button_jump_to_end, self);
144 }
145
146 void
set_length_adjustment(void)147 SaveMemoryWindow::set_length_adjustment(void)
148 {
149 gdouble value_start = gtk_adjustment_get_value(_w.adjustment_start);
150 gdouble value_end = gtk_adjustment_get_value(_w.adjustment_end);
151
152 // adjust upper bound before setting the new length value to prevent constraint problems
153 _w.adjustment_length->upper = _w.adjustment_start->upper + 1 - value_start;
154 g_signal_emit_by_name(G_OBJECT(_w.adjustment_length), "changed");
155
156 gdouble value_length = value_end - value_start + 1;
157 set_adjustment_value_with_blocked_handler(_w.adjustment_length, value_length, _w.on_adjustment_length_value_changed_id);
158 }
159
160 void
set_adjustment_value_with_blocked_handler(GtkAdjustment * adjustment,gdouble value,gint handler_id)161 SaveMemoryWindow::set_adjustment_value_with_blocked_handler(GtkAdjustment *adjustment, gdouble value, gint handler_id)
162 {
163 g_signal_handler_block(adjustment, handler_id);
164 gtk_adjustment_set_value(adjustment, value);
165 g_signal_handler_unblock(adjustment, handler_id);
166 }
167
168 void
on_populate_popup(GtkTextView * textview,GtkMenu * menu,gpointer user_data)169 SaveMemoryWindow::on_populate_popup(GtkTextView *textview, GtkMenu *menu, gpointer user_data)
170 {
171 #if 0
172 GtkWidget *separator = gtk_separator_menu_item_new();
173 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), separator);
174 gtk_widget_show(separator);
175
176 GtkWidget *bottom = gtk_image_menu_item_new_from_stock(GTK_STOCK_GOTO_BOTTOM, NULL);
177 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), bottom);
178 gtk_widget_show(bottom);
179
180 GtkWidget *top = gtk_image_menu_item_new_from_stock(GTK_STOCK_GOTO_TOP, NULL);
181 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), top);
182 gtk_widget_show(top);
183 #endif
184 }
185
186 void
jump_to(int line,gdouble within_margin,gboolean use_align,gdouble yalign)187 SaveMemoryWindow::jump_to(int line, gdouble within_margin, gboolean use_align, gdouble yalign)
188 {
189 GtkTextIter iter;
190 gtk_text_buffer_get_iter_at_line(_w.text_buffer, &iter, line);
191 gtk_text_view_scroll_to_iter(_w.text_view, &iter, within_margin, use_align, 0.0, yalign);
192 }
193
194 void
on_jump_to_start(GtkToggleButton * button,gpointer user_data)195 SaveMemoryWindow::on_jump_to_start(GtkToggleButton *button, gpointer user_data)
196 {
197 SaveMemoryWindow *self = (SaveMemoryWindow *)user_data;
198
199 if (gtk_toggle_button_get_active(button))
200 {
201 int start_addr = (int)gtk_adjustment_get_value(self->_w.adjustment_start);
202 self->jump_to(start_addr / 16, 0.0, TRUE, 0.1);
203 }
204 }
205
206 void
on_jump_to_end(GtkToggleButton * button,gpointer user_data)207 SaveMemoryWindow::on_jump_to_end(GtkToggleButton *button, gpointer user_data)
208 {
209 SaveMemoryWindow *self = (SaveMemoryWindow *)user_data;
210
211 if (gtk_toggle_button_get_active(button))
212 {
213 int end_addr = (int)gtk_adjustment_get_value(self->_w.adjustment_end);
214 self->jump_to(end_addr / 16, 0.05, FALSE, 0.0);
215 }
216 }
217
218 void
on_refresh(GtkButton * button,gpointer user_data)219 SaveMemoryWindow::on_refresh(GtkButton *button, gpointer user_data)
220 {
221 SaveMemoryWindow *self = (SaveMemoryWindow *)user_data;
222 self->refresh();
223 }
224
225 void
on_save(GtkButton * button,gpointer user_data)226 SaveMemoryWindow::on_save(GtkButton *button, gpointer user_data)
227 {
228 SaveMemoryWindow *self = (SaveMemoryWindow *)user_data;
229
230 GtkWidget *filechooser = gtk_file_chooser_dialog_new(_("Save As..."),
231 GTK_WINDOW(self->_window),
232 GTK_FILE_CHOOSER_ACTION_SAVE,
233 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
234 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
235 NULL);
236 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(filechooser), TRUE);
237
238 char buf[1024];
239 int start_addr = (int)gtk_adjustment_get_value(self->_w.adjustment_start);
240 snprintf(buf, sizeof(buf), _("memorydump_0x%04x.img"), start_addr);
241 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(filechooser), buf);
242
243 char *filename = NULL;
244 if (gtk_dialog_run(GTK_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
245 {
246 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
247 }
248
249 gtk_widget_destroy (filechooser);
250
251 if (filename == NULL)
252 return;
253
254 int end_addr = (int)gtk_adjustment_get_value(self->_w.adjustment_end);
255 self->save(filename, start_addr, end_addr);
256 self->hide();
257 g_free(filename);
258 }
259
260 void
save(const char * filename,int start_addr,int end_addr)261 SaveMemoryWindow::save(const char *filename, int start_addr, int end_addr)
262 {
263 CMD_Args *args = new CMD_Args();
264 args->set_string_arg("filename", filename);
265 args->set_long_arg("start-address", start_addr);
266 args->set_long_arg("end-address", end_addr);
267 CMD_EXEC_ARGS("kc-image-save", args);
268 delete args;
269 }
270
271 void
remove_selection(int start_line,int end_line)272 SaveMemoryWindow::remove_selection(int start_line, int end_line)
273 {
274 if (start_line == end_line)
275 return;
276
277 for (int a = start_line;a < end_line;a++)
278 line_selected[a] = false;
279
280 GtkTextIter iter, iter2;
281 gtk_text_buffer_get_iter_at_line(_w.text_buffer, &iter, start_line);
282 gtk_text_buffer_get_iter_at_line(_w.text_buffer, &iter2, end_line);
283 gtk_text_buffer_remove_tag(_w.text_buffer, _w.text_tag_bold, &iter, &iter2);
284 }
285
286 void
apply_line_selection(int line,int bytes,int line_offset,int line_offset2,int separator_offset,int chars_per_byte,bool add)287 SaveMemoryWindow::apply_line_selection(int line, int bytes, int line_offset, int line_offset2, int separator_offset, int chars_per_byte, bool add)
288 {
289 if (bytes == 0)
290 return;
291
292 GtkTextIter iter;
293 gtk_text_buffer_get_iter_at_line(_w.text_buffer, &iter, line);
294 if (!gtk_text_iter_forward_chars(&iter, line_offset))
295 return;
296
297 int chars = chars_per_byte * bytes + line_offset2;
298 if (bytes > 7)
299 chars += separator_offset;
300
301 GtkTextIter iter2 = iter;
302 if (!gtk_text_iter_forward_chars(&iter2, chars))
303 return;
304
305 if (add)
306 {
307 gtk_text_buffer_apply_tag(_w.text_buffer, _w.text_tag_bold, &iter, &iter2);
308 }
309 else
310 {
311 line_selected[line] = false;
312 gtk_text_buffer_remove_tag(_w.text_buffer, _w.text_tag_bold, &iter, &iter2);
313 }
314 }
315
316 void
apply_selection(int start_addr,int end_addr)317 SaveMemoryWindow::apply_selection(int start_addr, int end_addr)
318 {
319 int start_line = start_addr / 16;
320 int end_line = end_addr / 16;
321
322 // Remove bold tag from all lines before and after the selection
323 // _including_ the line that contains the last byte but _not_ the
324 // line that contains the first byte.
325 remove_selection(0, start_line);
326 remove_selection(end_line, NR_OF_LINES);
327
328 GtkTextIter iter, iter2;
329 for (int line = start_line;line < end_line;line++)
330 {
331 if (line_selected[line])
332 continue;
333
334 line_selected[line] = true;
335 gtk_text_buffer_get_iter_at_line(_w.text_buffer, &iter, line);
336 if (gtk_text_iter_forward_chars(&iter, 7))
337 {
338 gtk_text_buffer_get_iter_at_line(_w.text_buffer, &iter2, line + 1);
339 gtk_text_buffer_apply_tag(_w.text_buffer, _w.text_tag_bold, &iter, &iter2);
340 }
341 }
342
343 // order is important in case of start_line == end_line
344 apply_line_selection(end_line, end_addr % 16 + 1, 7, 0, 2, 3, true);
345 apply_line_selection(end_line, end_addr % 16 + 1, 56, 3, 3, 1, true);
346 apply_line_selection(start_line, start_addr % 16, 7, 0, 2, 3, false);
347 apply_line_selection(start_line, start_addr % 16, 56, 3, 3, 1, false);
348 }
349
350 void
load_memory(int start_addr,int end_addr)351 SaveMemoryWindow::load_memory(int start_addr, int end_addr)
352 {
353 char buf[20];
354
355 GtkTextIter iter, iter2;
356 for (int line_addr = 0;line_addr < 0x10000;line_addr += 16)
357 {
358 snprintf(buf, sizeof(buf), "%04xh: ", line_addr);
359 gtk_text_buffer_get_end_iter(_w.text_buffer, &iter);
360 gtk_text_buffer_insert_with_tags(_w.text_buffer, &iter, buf, -1, _w.text_tag_italic, NULL);
361
362 string line;
363 for (int idx = 0;idx < 16;idx++)
364 {
365 int addr = line_addr + idx;
366 snprintf(buf, sizeof(buf), "%02x%s", memory->memRead8(addr), (idx == 7) ? " | " : " ");
367 line += buf;
368 }
369 line += " ";
370 for (int idx = 0;idx < 16;idx++)
371 {
372 int addr = line_addr + idx;
373 byte_t b = memory->memRead8(addr);
374 int c = ((b >= 0x20) && (b < 0x7f)) ? b : '.';
375 snprintf(buf, sizeof(buf), "%c%s", c, (idx == 7) ? " | " : "");
376 line += buf;
377 }
378 line += "\n";
379
380 gtk_text_buffer_get_end_iter(_w.text_buffer, &iter);
381 gtk_text_buffer_insert(_w.text_buffer, &iter, line.c_str(), -1);
382 }
383
384 gtk_text_buffer_get_start_iter(_w.text_buffer, &iter);
385 gtk_text_buffer_get_end_iter(_w.text_buffer, &iter2);
386 gtk_text_buffer_apply_tag(_w.text_buffer, _w.text_tag_monospace, &iter, &iter2);
387
388 apply_selection(start_addr, end_addr);
389 }
390
refresh(void)391 void SaveMemoryWindow::refresh(void)
392 {
393 gtk_text_buffer_set_text(_w.text_buffer, "", 0);
394
395 for (int a = 0;a < NR_OF_LINES;a++)
396 line_selected[a] = false;
397
398 if (is_visible())
399 {
400 int start_addr = (int)gtk_adjustment_get_value(_w.adjustment_start);
401 int end_addr = (int)gtk_adjustment_get_value(_w.adjustment_end);
402 load_memory(start_addr, end_addr);
403 }
404 }
405
406 void
show(void)407 SaveMemoryWindow::show(void)
408 {
409 UI_Gtk_Window::show();
410
411 refresh();
412 }
413
414 void
init(void)415 SaveMemoryWindow::init(void)
416 {
417 _window = get_widget("save_memory_window");
418 gtk_signal_connect(GTK_OBJECT(_window), "delete_event",
419 GTK_SIGNAL_FUNC(cmd_exec_sft),
420 (char *)"ui-save-memory-window-toggle"); // FIXME:
421
422 _w.button_save = GTK_BUTTON(get_widget("dialog_button_save"));
423 g_signal_connect(_w.button_save, "clicked", G_CALLBACK(on_save), this);
424
425 _w.button_refresh = GTK_BUTTON(get_widget("dialog_button_refresh"));
426 g_signal_connect(_w.button_refresh, "clicked", G_CALLBACK(on_refresh), this);
427
428 _w.toggle_button_jump_to_start = GTK_TOGGLE_BUTTON(get_widget("toggle_button_jump_to_start"));
429 g_signal_connect(_w.toggle_button_jump_to_start, "toggled", G_CALLBACK(on_jump_to_start), this);
430
431 _w.toggle_button_jump_to_end = GTK_TOGGLE_BUTTON(get_widget("toggle_button_jump_to_end"));
432 g_signal_connect(_w.toggle_button_jump_to_end, "toggled", G_CALLBACK(on_jump_to_end), this);
433
434 _w.spin_button_start_hex = GTK_SPIN_BUTTON(get_widget("spin_button_start_hex"));
435 _w.spin_button_start_dec = GTK_SPIN_BUTTON(get_widget("spin_button_start_dec"));
436 _w.spin_button_end_hex = GTK_SPIN_BUTTON(get_widget("spin_button_end_hex"));
437 _w.spin_button_end_dec = GTK_SPIN_BUTTON(get_widget("spin_button_end_dec"));
438 _w.spin_button_length_hex = GTK_SPIN_BUTTON(get_widget("spin_button_length_hex"));
439 _w.spin_button_length_dec = GTK_SPIN_BUTTON(get_widget("spin_button_length_dec"));
440
441 _w.adjustment_start = gtk_spin_button_get_adjustment(_w.spin_button_start_hex);
442 _w.adjustment_end = gtk_spin_button_get_adjustment(_w.spin_button_end_hex);
443 _w.adjustment_length = gtk_spin_button_get_adjustment(_w.spin_button_length_hex);
444
445 gtk_spin_button_set_adjustment(_w.spin_button_start_dec, _w.adjustment_start);
446 gtk_spin_button_set_adjustment(_w.spin_button_end_dec, _w.adjustment_end);
447 gtk_spin_button_set_adjustment(_w.spin_button_length_dec, _w.adjustment_length);
448
449 g_signal_connect(_w.spin_button_start_hex, "output", G_CALLBACK(on_output), NULL);
450 g_signal_connect(_w.spin_button_end_hex, "output", G_CALLBACK(on_output), NULL);
451 g_signal_connect(_w.spin_button_length_hex, "output", G_CALLBACK(on_output), NULL);
452
453 g_signal_connect(_w.spin_button_start_hex, "input", G_CALLBACK(on_input), NULL);
454 g_signal_connect(_w.spin_button_end_hex, "input", G_CALLBACK(on_input), NULL);
455 g_signal_connect(_w.spin_button_length_hex, "input", G_CALLBACK(on_input), NULL);
456
457 _w.on_adjustment_start_value_changed_id = g_signal_connect(_w.adjustment_start, "value-changed", G_CALLBACK(on_adjustment_start_value_changed), this);
458 _w.on_adjustment_end_value_changed_id = g_signal_connect(_w.adjustment_end, "value-changed", G_CALLBACK(on_adjustment_end_value_changed), this);
459 _w.on_adjustment_length_value_changed_id = g_signal_connect(_w.adjustment_length, "value-changed", G_CALLBACK(on_adjustment_length_value_changed), this);
460
461 _w.text_view = GTK_TEXT_VIEW(get_widget("textview"));
462 _w.text_buffer = gtk_text_view_get_buffer(_w.text_view);
463 _w.text_tag_bold = gtk_text_buffer_create_tag(_w.text_buffer, "bold",
464 "weight", PANGO_WEIGHT_BOLD,
465 "background", "#D0D0D0",
466 "background-set", TRUE,
467 NULL);
468 _w.text_tag_italic = gtk_text_buffer_create_tag(_w.text_buffer, "italic", "style", PANGO_STYLE_ITALIC, NULL);
469 _w.text_tag_monospace = gtk_text_buffer_create_tag(_w.text_buffer, "monospace", "family", "Monospace", NULL);
470 g_signal_connect(_w.text_view, "populate-popup", G_CALLBACK(on_populate_popup), this);
471
472 init_dialog("ui-save-memory-window-toggle", "window-save-memory");
473 }
474