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