1 module lem1802_fontview;
2 
3 import core.stdc.process, std.stdio, std.conv, std.math;
4 
5 import gtkc.gtktypes;
6 import gtk.Main, gtk.Builder;
7 import gtk.Widget, gtk.Window, gtk.MainWindow, gtk.Dialog, gtk.AboutDialog;
8 import gtk.Button, gtk.Label, gtk.MenuBar, gtk.MenuItem, gtk.ToggleButton;
9 import gtk.SpinButton, gtk.Adjustment, gtk.AccelGroup;
10 import gtk.DrawingArea;
11 import gdk.Event, gtk.Container, gdk.RGBA;
12 
13 import cairo.Context, cairo.Surface;
14 
15 import ui.file_chooser, ui.dialog_slice;
16 import dcpu.ram_io;
17 
18 string filename;            /// Open file
19 TypeHexFile type;           /// Type of file
20 Window mainwin;             /// Main window
21 
22 ushort[256] font;           /// Font data
23 size_t selected;            /// Selected gryph
24 
25 DrawingArea dwa;            /// Drawing widget
26 enum FONT_GLYPHS = 128;
27 enum G_WIDTH  = 4;
28 enum G_HEIGHT = 8;
29 enum uint MATRIX_WIDTH  = 32;
30 enum uint MATRIX_HEIGHT = 4;
31 enum RECT_SIZE = 4;
32 enum CELL_HEIGHT = RECT_SIZE*G_HEIGHT;
33 enum CELL_WIDTH  = RECT_SIZE*G_WIDTH;
34 enum double min_width  = G_WIDTH*RECT_SIZE*MATRIX_WIDTH +30; // Min width of drawing widget
35 enum double min_height = G_HEIGHT*RECT_SIZE*MATRIX_HEIGHT +3; // Min height of drawing widget
36 
37 Label lbl_pos;              // Label with selected glyph position
38 Label lbl_bin;              // Label with binary representation of selected glyph
39 Label lbl_hex;              // Label with hex representation of selected glyph
40 Label lbl_dec;              // Label with decimal representation of selected glyph
41 
42 //bool updating;            // Updating data form out to the editor ?
43 DrawingArea glyph_editor;   // Glyph editor
44 //ToggleButton[16][2] editor; // Editor toggle buttons
45 
46 AboutDialog win_about;      // About dialog
47 
48 size_t file_size;           // Original File size
49 
50 /**
51  * Close the App when it's clicked menu exit option
52  */
53 extern (C) export void on_mnu_exit_activate (Event event, Widget widget) {
54   Main.quit();
55 }
56 
57 /**
58  * Show About dialog
59  */
60 extern (C) export void on_mnu_about_activate (Event event, Widget widget) {
61   win_about.run();
62   win_about.hide();
63 }
64 
65 /**
66  * Click over Previus button
67  */
68 extern (C) export void on_but_prev_clicked (Event event, Widget widget) {
69   selected = (selected -1) % 128;
70   lbl_pos.setLabel(to!string(selected));
71   update_editor();
72   dwa.queueDraw();
73 }
74 
75 /**
76  * Click over Previus button
77  */
78 extern (C) export void on_but_next_clicked (Event event, Widget widget) {
79   selected = (selected +1) % 128;
80   lbl_pos.setLabel(to!string(selected));
81   update_editor();
82   dwa.queueDraw();
83 }
84 
85 /**
86  * Reset all data (new font)
87  */
88 extern (C) export void on_mnu_new_activate (Event event, Widget widget) {
89   filename = "";
90   selected = 0;
91   font[] = 0;
92   update_editor();
93   dwa.queueDraw();
94 }
95 
96 
97 /**
98  * Show the Open file Dialog and try to load it
99  */
100 extern (C) export void on_mnu_open_activate (Event event, Widget widget) {
101   auto opener = new FileOpener(mainwin);
102   immutable response = opener.run();
103   if (response == ResponseType.ACCEPT) {
104     filename = opener.getFilename();
105     type = opener.type;
106 
107     ushort[] tmp;
108     if (filename !is null && filename.length > 0){
109       try {
110         tmp = load_ram(type, filename);
111       } catch (Exception e) {
112         stderr.writeln("Error: Couldn't open file\n", e.msg);
113       }
114 
115       if (tmp.length > 256) { // Contains something more that a LEM1802 font
116         file_size = tmp.length;
117         auto d = new dialog_slice("LEM1802 Font View", mainwin, GtkDialogFlags.MODAL,
118             "The file contains more data that a font for the LEM1802.
119 You must select a range of data that you desire to load like a font.", file_size, 255, false);
120         d.show();
121         immutable auto r = d.run();
122         d.hide();
123         if ( r == ResponseType.ACCEPT) {
124           size_t slice = cast(size_t)(d.size);
125           size_t b = cast(size_t)d.bottom_address;
126           size_t e = cast(size_t)d.top_address;
127           e++;
128 
129           if (((e-b+1)%2) != 0 && (e-b > 2)) {
130             e--; // Clamp the last half glyph selected
131             slice--;
132           }
133 
134           font[0..slice] = tmp[b..e];
135           font[slice..$] = 0;
136         }
137       } else {
138         font[0..tmp.length] = tmp[0..tmp.length];
139         font[tmp.length..$] = 0;
140       }
141       // Updates GUI
142       selected = 0;
143       update_editor();
144       dwa.queueDraw();
145     }
146   }
147   opener.hide();
148   opener.destroy();
149 }
150 
151 /**
152  * Show the Save file Dialog and try to save it
153  */
154 extern (C) export void on_mnu_saveas_activate (Event event, Widget widget) {
155   auto opener = new FileOpener(mainwin, false);
156   immutable auto response = opener.run();
157   if (response == ResponseType.ACCEPT) {
158     filename = opener.getFilename();
159     type = opener.type;
160     stderr.writeln("Type :", type);
161     // Save data
162     try {
163       save_ram(type, filename, font);
164     } catch (Exception e) {
165       stderr.writeln("Error: Couldn't save data\n", e.msg);
166     }
167   }
168 
169   opener.hide();
170   opener.destroy();
171 }
172 
173 /**
174  * Update the state of the editor buttons
175  */
176 void update_editor() {
177   glyph_editor.queueDraw();
178   update_glyph_lbl(); // Update labels at same time
179 }
180 
181 /**
182  * Updates binary, hex and decimal representation of selected glyph
183  */
184 void update_glyph_lbl() {
185   import std..string : format;
186   lbl_bin.setLabel("0b"~format("%016b",font[selected*2])~"\n"~ "0b"~format("%016b",font[selected*2+1]));
187   lbl_hex.setLabel("0x"~format("%04X",font[selected*2])~"\n"~ "0x"~format("%04X",font[selected*2+1]));
188   lbl_dec.setLabel(to!string(font[selected*2])~"\n"~to!string(font[selected*2+1]));
189 }
190 
191 void main(string[] args) {
192   Main.init(args);
193 
194   auto builder = new Builder ();
195 
196   if (! builder.addFromString (import("fview.ui"))) {
197     writefln("Oops, could not create Builder object, check your builder file ;)");
198     exit(1);
199   }
200 
201   // Get reference to Objects
202   mainwin = cast(Window) builder.getObject ("win_fontview");
203   if (mainwin is null) {
204     writefln("Can't find win_fontview widget");
205     exit(1);
206   }
207   auto accelgroup = cast(AccelGroup) builder.getObject ("accelgroup1");
208   if (accelgroup is null) {
209     writefln("Can't find accelgroup1 widget");
210     exit(1);
211   }
212   mainwin.addAccelGroup(accelgroup);
213 
214   win_about = cast(AboutDialog) builder.getObject ("win_about");
215   if (mainwin is null) {
216     writefln("Can't find win_about widget");
217     exit(1);
218   }
219 
220 
221   dwa = cast(DrawingArea) builder.getObject("dwa_general");
222   if (dwa is null) {
223     writefln("Can't find dwa_general widget");
224     exit(1);
225   }
226 
227   lbl_pos = cast(Label) builder.getObject("lbl_pos");
228   if (lbl_pos is null) {
229     writefln("Can't find lbl_pos widget");
230     exit(1);
231   }
232   lbl_bin = cast(Label) builder.getObject ("lbl_bin");
233   if (lbl_bin is null) {
234     writefln("Can't find lbl_bin widget");
235     exit(1);
236   }
237   lbl_hex = cast(Label) builder.getObject ("lbl_hex");
238   if (lbl_hex is null) {
239     writefln("Can't find lbl_hex widget");
240     exit(1);
241   }
242   lbl_dec = cast(Label) builder.getObject ("lbl_dec");
243   if (lbl_dec is null) {
244     writefln("Can't find lbl_dec widget");
245     exit(1);
246   }
247 
248   glyph_editor = cast(DrawingArea) builder.getObject ("glyph_editor");
249   if (glyph_editor is null) {
250     writefln("Can't find glyph_editor widget");
251     exit(1);
252   }
253 
254   dwa.overrideBackgroundColor( GtkStateFlags.NORMAL, new RGBA(0, 0, 0));
255   glyph_editor.overrideBackgroundColor( GtkStateFlags.NORMAL, new RGBA(0, 0, 0));
256   // Here we assing event handlers --------------------------------------------
257 
258   // Closing the window ends the program
259   mainwin.addOnDestroy ( (Widget w) {
260     Main.quit();
261   });
262 
263   // Select a Glyph
264   dwa.addOnButtonPress ( (Event event, Widget widget) {
265     if (event !is null) {
266       GtkAllocation size;
267       widget.getAllocation(size);
268 
269       double x = event.button().x *(min_width / size.width);   // Scales coords to be the same
270       double y = event.button().y *(min_height / size.height); // always with diferent geometry
271 
272       x = floor(x / (G_WIDTH*RECT_SIZE  +1));
273       y = floor(y / (G_HEIGHT*RECT_SIZE +1));
274       selected = (to!size_t(x+ y*MATRIX_WIDTH) % FONT_GLYPHS);
275 
276       lbl_pos.setLabel(to!string(selected));
277       dwa.queueDraw();
278       update_editor();
279 
280       return true;
281     }
282     return false;
283   });
284 
285   // Draws Glyphs viewer
286   dwa.addOnDraw( (Scoped!Context cr, Widget widget) {
287     GtkAllocation size;
288     widget.getAllocation(size);
289 
290     // Calcs factor scale
291     immutable double scale_x = size.width / min_width;
292     immutable double scale_y = size.height / min_height;
293     cr.scale(scale_x, scale_y);
294 
295     // Draw font on a 32x4 matrix. Each font[i] is half glyph
296     cr.save();
297     for (size_t i; i< font.length; i++) {
298       auto hcell_x = i % (2*MATRIX_WIDTH);
299       auto cell_y = i / (2*MATRIX_WIDTH);
300       auto x_org = hcell_x*CELL_WIDTH/2 + floor(hcell_x/2.0);
301       auto y_org = cell_y * (CELL_HEIGHT+1);
302 
303       for (ushort p; p < 16; p++) { // And loops each pixel of a half glyph
304         if(( font[i] & (1<<p)) != 0) {
305           immutable int l_oct = 1 - (p / 8);
306           double x = l_oct * RECT_SIZE;
307           x += x_org;
308 
309           double y = (p % 8) * RECT_SIZE;
310           y += y_org;
311 
312           cr.rectangle(x, y, RECT_SIZE, RECT_SIZE);
313           cr.setSourceRgb(1.0, 1.0, 1.0);
314           cr.fill();
315         }
316       }
317     }
318     cr.restore();
319 
320     // Draw lines around gryphs
321     cr.save();
322       cr.setSourceRgb(1.0, 0, 0);
323       cr.setLineWidth(1.0);
324       for (auto y = CELL_HEIGHT +1 ; y< (CELL_HEIGHT+1)*MATRIX_HEIGHT; y+=CELL_HEIGHT+1) {
325         cr.moveTo(0, y);
326         cr.lineTo(min_width, y);
327       }
328       for (auto x = CELL_WIDTH+1; x< (CELL_WIDTH+1)*MATRIX_WIDTH; x+=CELL_WIDTH+1) {
329         cr.moveTo(x, 0);
330         cr.lineTo(x, min_height);
331       }
332       cr.stroke();
333 
334     cr.restore();
335 
336     // Draw rectangle around selected glyph
337     cr.save();
338       cr.setSourceRgb(0, 1.0, 0);
339       cr.setLineWidth(1.5);
340       immutable double x = (selected%MATRIX_WIDTH)*G_WIDTH*RECT_SIZE + (selected%MATRIX_WIDTH);
341       immutable double y = (selected / MATRIX_WIDTH)*(CELL_HEIGHT+1);
342       cr.rectangle(x, y, RECT_SIZE*G_WIDTH, RECT_SIZE*G_HEIGHT);
343       cr.stroke();
344 
345       cr.restore();
346     return false;
347   });
348 
349   // Draws Glyph editor
350   glyph_editor.addOnDraw( (Scoped!Context cr, Widget widget) {
351     GtkAllocation size;
352     widget.getAllocation(size);
353     /+
354 
355       //cr.scale(scale_x, scale_y);
356       cr.translate(0, 0);
357 
358     // Calcs factor scale
359     double scale_x = size.width / min_width;
360     double scale_y = size.height / min_height;
361 +/
362 
363     // Draw lines around gryphs
364     cr.save();
365       cr.setSourceRgb(0.5, 0.5, 0.5);
366       cr.setLineWidth(1.0);
367       for (auto y = 20.0; y< 21*8; y+=21) {
368         cr.moveTo(0, y);
369         cr.lineTo(size.width, y);
370       }
371       for (auto x = 20.0; x< 21*4; x+=21) {
372         cr.moveTo(x, 0);
373         cr.lineTo(x, size.height);
374       }
375       cr.stroke();
376     cr.restore();
377 
378     // Paints selected Glyph
379     cr.save();
380     for (int x; x < 2; x++) {
381       for (int y; y < 16; y++) {
382         if (( font[selected*2+x] & (1<<y)) != 0) {
383           auto pos_x = (x*2 + 1 - floor(cast(double)(y/8)) ) * 21;
384           cr.rectangle(pos_x, (y%8)*21, 20, 20);
385           cr.setSourceRgb(1.0, 1.0, 1.0);
386           cr.fill();
387         }
388       }
389     }
390     cr.restore();
391 
392     return false;
393   });
394 
395   // Update the changes of edited glyph
396   glyph_editor.addOnButtonPress( (Event event, Widget widget) {
397     if (event !is null) {
398       auto x = cast(ushort) floor(event.button().x / (20.0 +1));
399       auto y = cast(ushort) floor(event.button().y / (20.0 +1));
400 
401       if ( x < 1) { // First column
402         font[selected*2] = font[selected*2] ^ cast(ushort)(1<<(y+8));
403       } else if ( x < 2) { // Second column
404         font[selected*2] = font[selected*2] ^ cast(ushort)(1<<y);
405       } else if ( x < 3) { // Third column
406         font[selected*2 +1] = font[selected*2 +1] ^ cast(ushort)(1<<(y+8));
407       } else if ( x < 4) { // Fourth column
408         font[selected*2 +1] = font[selected*2 +1] ^ cast(ushort)(1<<y);
409       }
410 
411       dwa.queueDraw();
412       update_editor();
413 
414       return true;
415     }
416     return false;
417     /*
418       for (int x; x < 2; x++) {
419       for (int y; y <16; y++) {
420         auto pos = 1<<y;
421         r ~= "editor["~to!string(x)~"]["~to!string(y)~"]";
422         r ~= ".addOnClicked( (Button b) {";
423         r ~= " if (!updating) {";
424         if (x != 1) {
425           r ~= "  font[selected*2] = font[selected*2] ^ "~to!string(pos)~";";
426         } else {
427           r ~= "  font[selected*2 +1] = font[selected*2 +1] ^ "~to!string(pos)~";";
428         }
429         r ~= " dwa.queueDraw(); update_glyph_lbl;";
430         r ~= " }";
431         r ~= "});";
432       }
433     }
434     */
435   });
436 
437   builder.connectSignals (null); // This connect signals defiend on the builder with "extern (C) export" edifned functions
438   mainwin.show ();
439 
440   Main.run();
441 }