1 module lem1802_fontview;
2 
3 import std.c.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   auto 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         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   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;
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   int old_w, old_h;
193   Main.init(args);
194 
195   auto builder = new Builder ();
196 
197   if (! builder.addFromString (import("fview.ui"))) {
198     writefln("Oops, could not create Builder object, check your builder file ;)");
199     exit(1);
200   }
201 
202   // Get reference to Objects
203   mainwin = cast(Window) builder.getObject ("win_fontview");
204   if (mainwin is null) {
205     writefln("Can't find win_fontview widget");
206     exit(1);
207   }
208   auto accelgroup = cast(AccelGroup) builder.getObject ("accelgroup1");
209   if (accelgroup is null) {
210     writefln("Can't find accelgroup1 widget");
211     exit(1);
212   }
213   mainwin.addAccelGroup(accelgroup);
214 
215   win_about = cast(AboutDialog) builder.getObject ("win_about");
216   if (mainwin is null) {
217     writefln("Can't find win_about widget");
218     exit(1);
219   }
220 
221 
222   dwa = cast(DrawingArea) builder.getObject("dwa_general");
223   if (dwa is null) {
224     writefln("Can't find dwa_general widget");
225     exit(1);
226   }
227 
228   lbl_pos = cast(Label) builder.getObject("lbl_pos");
229   if (lbl_pos is null) {
230     writefln("Can't find lbl_pos widget");
231     exit(1);
232   }
233   lbl_bin = cast(Label) builder.getObject ("lbl_bin");
234   if (lbl_bin is null) {
235     writefln("Can't find lbl_bin widget");
236     exit(1);
237   }
238   lbl_hex = cast(Label) builder.getObject ("lbl_hex");
239   if (lbl_hex is null) {
240     writefln("Can't find lbl_hex widget");
241     exit(1);
242   }
243   lbl_dec = cast(Label) builder.getObject ("lbl_dec");
244   if (lbl_dec is null) {
245     writefln("Can't find lbl_dec widget");
246     exit(1);
247   }
248 
249   glyph_editor = cast(DrawingArea) builder.getObject ("glyph_editor");
250   if (glyph_editor is null) {
251     writefln("Can't find glyph_editor widget");
252     exit(1);
253   }
254 
255   dwa.overrideBackgroundColor( GtkStateFlags.NORMAL, new RGBA(0, 0, 0));
256   glyph_editor.overrideBackgroundColor( GtkStateFlags.NORMAL, new RGBA(0, 0, 0));
257   // Here we assing event handlers --------------------------------------------
258 
259   // Closing the window ends the program
260   mainwin.addOnDestroy ( (Widget w) {
261     Main.quit();
262   });
263 
264   // Select a Glyph
265   dwa.addOnButtonPress ( (Event event, Widget widget) {
266     if (event !is null) {
267       GtkAllocation size;
268       widget.getAllocation(size);
269 
270       double x = event.button().x *(min_width / size.width);   // Scales coords to be the same
271       double y = event.button().y *(min_height / size.height); // always with diferent geometry
272 
273       x = floor(x / (G_WIDTH*RECT_SIZE  +1));
274       y = floor(y / (G_HEIGHT*RECT_SIZE +1));
275       selected = (to!size_t(x+ y*MATRIX_WIDTH) % FONT_GLYPHS);
276 
277       lbl_pos.setLabel(to!string(selected));
278       dwa.queueDraw();
279       update_editor();
280 
281       return true;
282     }
283     return false;
284   });
285 
286   // Draws Glyphs viewer
287   dwa.addOnDraw( (Scoped!Context cr, Widget widget) {
288     GtkAllocation size;
289     widget.getAllocation(size);
290 
291     // Calcs factor scale
292     double scale_x = size.width / min_width;
293     double scale_y = size.height / min_height;
294     cr.scale(scale_x, scale_y);
295 
296     // Draw font on a 32x4 matrix. Each font[i] is half glyph
297     cr.save();
298     for (size_t i; i< font.length; i++) {
299       auto hcell_x = i % (2*MATRIX_WIDTH);
300       auto cell_y = i / (2*MATRIX_WIDTH);
301       auto x_org = hcell_x*CELL_WIDTH/2 + floor(hcell_x/2.0);
302       auto y_org = cell_y * (CELL_HEIGHT+1);
303 
304       for (ushort p; p < 16; p++) { // And loops each pixel of a half glyph
305         if(( font[i] & (1<<p)) != 0) {
306           int l_oct = 1 - (p / 8);
307           double x = l_oct * RECT_SIZE;
308           x += x_org;
309 
310           double y = (p % 8) * RECT_SIZE;
311           y += y_org;
312 
313           cr.rectangle(x, y, RECT_SIZE, RECT_SIZE);
314           cr.setSourceRgb(1.0, 1.0, 1.0);
315           cr.fill();
316         }
317       }
318     }
319     cr.restore();
320 
321     // Draw lines around gryphs
322     cr.save();
323       cr.setSourceRgb(1.0, 0, 0);
324       cr.setLineWidth(1.0);
325       for (auto y = CELL_HEIGHT +1 ; y< (CELL_HEIGHT+1)*MATRIX_HEIGHT; y+=CELL_HEIGHT+1) {
326         cr.moveTo(0, y);
327         cr.lineTo(min_width, y);
328       }
329       for (auto x = CELL_WIDTH+1; x< (CELL_WIDTH+1)*MATRIX_WIDTH; x+=CELL_WIDTH+1) {
330         cr.moveTo(x, 0);
331         cr.lineTo(x, min_height);
332       }
333       cr.stroke();
334 
335     cr.restore();
336 
337     // Draw rectangle around selected glyph
338     cr.save();
339       cr.setSourceRgb(0, 1.0, 0);
340       cr.setLineWidth(1.5);
341       double x = (selected%MATRIX_WIDTH)*G_WIDTH*RECT_SIZE + (selected%MATRIX_WIDTH);
342       double y = (selected / MATRIX_WIDTH)*(CELL_HEIGHT+1);
343       cr.rectangle(x, y, RECT_SIZE*G_WIDTH, RECT_SIZE*G_HEIGHT);
344       cr.stroke();
345 
346       cr.restore();
347     return false;
348   });
349 
350   // Draws Glyph editor
351   glyph_editor.addOnDraw( (Scoped!Context cr, Widget widget) {
352     GtkAllocation size;
353     widget.getAllocation(size);
354     /+
355 
356       //cr.scale(scale_x, scale_y);
357       cr.translate(0, 0);
358 
359     // Calcs factor scale
360     double scale_x = size.width / min_width;
361     double scale_y = size.height / min_height;
362 +/
363 
364     // Draw lines around gryphs
365     cr.save();
366       cr.setSourceRgb(0.5, 0.5, 0.5);
367       cr.setLineWidth(1.0);
368       for (auto y = 20.0; y< 21*8; y+=21) {
369         cr.moveTo(0, y);
370         cr.lineTo(size.width, y);
371       }
372       for (auto x = 20.0; x< 21*4; x+=21) {
373         cr.moveTo(x, 0);
374         cr.lineTo(x, size.height);
375       }
376       cr.stroke();
377     cr.restore();
378 
379     // Paints selected Glyph
380     cr.save();
381     for (int x; x < 2; x++) {
382       for (int y; y < 16; y++) {
383         if (( font[selected*2+x] & (1<<y)) != 0) {
384           auto pos_x = (x*2 + 1 - floor(cast(double)(y/8)) ) * 21;
385           cr.rectangle(pos_x, (y%8)*21, 20, 20);
386           cr.setSourceRgb(1.0, 1.0, 1.0);
387           cr.fill();
388         }
389       }
390     }
391     cr.restore();
392 
393     return false;
394   });
395 
396   // Update the changes of edited glyph
397   glyph_editor.addOnButtonPress( (Event event, Widget widget) {
398     if (event !is null) {
399       auto x = cast(ushort) floor(event.button().x / (20.0 +1));
400       auto y = cast(ushort) floor(event.button().y / (20.0 +1));
401 
402       if ( x < 1) { // First column
403         font[selected*2] = font[selected*2] ^ cast(ushort)(1<<(y+8));
404       } else if ( x < 2) { // Second column
405         font[selected*2] = font[selected*2] ^ cast(ushort)(1<<y);
406       } else if ( x < 3) { // Third column
407         font[selected*2 +1] = font[selected*2 +1] ^ cast(ushort)(1<<(y+8));
408       } else if ( x < 4) { // Fourth column
409         font[selected*2 +1] = font[selected*2 +1] ^ cast(ushort)(1<<y);
410       }
411 
412       dwa.queueDraw();
413       update_editor();
414 
415       return true;
416     }
417     return false;
418     /*
419       for (int x; x < 2; x++) {
420       for (int y; y <16; y++) {
421         auto pos = 1<<y;
422         r ~= "editor["~to!string(x)~"]["~to!string(y)~"]";
423         r ~= ".addOnClicked( (Button b) {";
424         r ~= " if (!updating) {";
425         if (x != 1) {
426           r ~= "  font[selected*2] = font[selected*2] ^ "~to!string(pos)~";";
427         } else {
428           r ~= "  font[selected*2 +1] = font[selected*2 +1] ^ "~to!string(pos)~";";
429         }
430         r ~= " dwa.queueDraw(); update_glyph_lbl;";
431         r ~= " }";
432         r ~= "});";
433       }
434     }
435     */
436   });
437 
438   builder.connectSignals (null); // This connect signals defiend on the builder with "extern (C) export" edifned functions
439   mainwin.show ();
440 
441   Main.run();
442 }