import std.stdio; import std.file; import std.string; import std.conv : to; import gtk.Main; import gtk.MainWindow; import gtk.TreeView; import gtk.ListStore; import gtk.TreeViewColumn; import gtk.TreeIter; import gtk.Label; import gtk.Entry; import gtk.CellRenderer; import gtk.CellRendererText; import gtk.VBox; import gtk.Statusbar; import gtk.Button; import gtk.ScrolledWindow; import gtk.HBox; void main(string[] args) { Main.init(args); //Window auto window = new MainWindow("2DA-Edit"); auto cont = new VBox(false, 0); window.add(cont); cont.setSizeRequest(300, 200); auto buttonSave = new Button("document-save-symbolic", GtkIconSize.MENU); buttonSave.setTooltipText("Save"); auto buttonOpen = new Button("document-open-symbolic", GtkIconSize.MENU); buttonOpen.setTooltipText("Open 2DA"); auto buttonInsert = new Button("format-text-direction-ltr-symbolic", GtkIconSize.SMALL_TOOLBAR); buttonInsert.setTooltipText("Insert row after"); auto buttonDelete = new Button("user-trash-symbolic", GtkIconSize.SMALL_TOOLBAR); buttonDelete.setTooltipText("Delete row"); auto buttonRenumber = new Button("view-list-symbolic", GtkIconSize.SMALL_TOOLBAR); buttonRenumber.setTooltipText("Renumber all rows"); version(Windows){ //Menu bar import gtk.HBox; auto cont2 = new HBox(false, 0); cont.packStart(cont2, false, false, 0); cont2.packStart(buttonOpen, false, false, 0); cont2.packEnd(buttonSave, false, false, 0); } else{ //Header bar import gtk.HeaderBar; auto header = new HeaderBar(); window.setTitlebar(header); header.setTitle("2DAEdit"); header.setProperty("show-close-button", true); header.packStart(buttonOpen); header.packEnd(buttonSave); } //Status bar auto statusbar = new HBox(false, 0); cont.packEnd(statusbar, false, false, 5); statusbar.packStart(buttonRenumber, false, false, 5); statusbar.packStart(buttonInsert, false, false, 5); statusbar.packStart(buttonDelete, false, false, 5); //TreeView to display database auto scroll = new ScrolledWindow(PolicyType.AUTOMATIC, PolicyType.AUTOMATIC); cont.packEnd(scroll, true, true, 0); auto tree = new TreeView(); scroll.add(tree); tree.setHeadersVisible(true); tree.setEnableSearch(true); tree.setProperty("enable-grid-lines", GtkTreeViewGridLines.VERTICAL); tree.setProperty("tooltip-column", 0); tree.setProperty("reorderable", true); tree.setProperty("headers-clickable", true); //Configure button callbacks buttonSave.addOnClicked((Button){ Save(tree); }); buttonOpen.addOnClicked((Button){ import gtk.Dialog; import gtk.FileChooserDialog; auto fc = new FileChooserDialog("Open 2DA", window, FileChooserAction.OPEN); auto res = fc.run(); if(res==GtkResponseType.OK){ string filename = fc.getFilename(); version(Windows) Open(filename, tree, cast(Object)window); else Open(filename, tree, cast(Object)header); } fc.destroy(); }); buttonInsert.addOnClicked((Button){ TreeIter it = tree.getSelectedIter(); auto store = cast(ListStore)tree.getModel(); if(store !is null){ store.insertAfter(it, it); store.setValue(it, 0, 0); foreach(i ; 1..store.getNColumns()){ store.setValue(it, cast(int)i, "_"); } } }); buttonDelete.addOnClicked((Button){ TreeIter it = tree.getSelectedIter(); auto store = cast(ListStore)tree.getModel(); if(store !is null) store.remove(it); }); buttonRenumber.addOnClicked((Button){ auto store = cast(ListStore)tree.getModel(); if(store !is null){ TreeIter it = new TreeIter(); if(store.getIterFirst(it)){ int id = 0; do{ store.setValue(it, 0, id++); }while(store.iterNext(it)); } } }); //Open if exists if(args.length>=2 && exists(args[1])){ version(Windows) Open(args[1], tree, cast(Object)window); else Open(args[1], tree, cast(Object)header); } window.showAll(); Main.run(); } void Save(ref TreeView tree){ auto store = cast(ListStore)tree.getModel(); if(store !is null){ auto file = File(openedFile, "w"); foreach(i ; 0..tree.getNColumns) file.write(tree.getColumn(i).getTitle, "\t"); file.write("\n"); TreeIter it = new TreeIter(); if(store.getIterFirst(it)){ do{ file.write(store.getValueInt(it, 0)); foreach(i ; 1..store.getNColumns()){ file.write("\t\"",store.getValueString(it, i),"\""); } file.write("\n"); }while(store.iterNext(it)); file.flush(); file.close(); writeln("File written: ",openedFile); } } else writeln("Nothing to save !"); } string openedFile; void Open(string file, ref TreeView tree, Object header){ auto twoda = new TwoDA(file); openedFile = file; version(Windows) (cast(Window)header).setTitle(file); else{ import gtk.HeaderBar; (cast(HeaderBar)header).setSubtitle(file); } //Delete old store auto oldstore = cast(ListStore)tree.getModel(); if(oldstore !is null) oldstore.destroy(); //Remove columns from TreeView foreach(i ; 0..tree.getNColumns) tree.removeColumn(tree.getColumn(0)); //Set store types GType type[]; type~=GType.INT; foreach(i;1..twoda.header.length)type~=GType.STRING; //Create new store auto store = new ListStore(type); tree.setModel(store); //Setup TreeView columns foreach(index, s ; twoda.header){ CellRendererText cr = new CellRendererText(); cr.setProperty("editable", true); if(index==0){ cr.setProperty("background-rgba", cast(ulong)(new GdkRGBA(0.36, 0.13, 0.4, 1.0))); cr.setProperty("background-set", true); cr.addOnEdited((string path, string newval, CellRendererText crt){ try{ int n = newval.to!int; TreeIter t = new TreeIter(tree.getModel(), path); store.setValue(t, cast(int)crt.getData("colnumber"), n); } catch(Exception e){ writeln("Not a number"); } }); } else{ cr.addOnEdited((string path, string newval, CellRendererText crt){ TreeIter t = new TreeIter(tree.getModel(), path); store.setValue(t, cast(int)crt.getData("colnumber"), newval); }); } cr.setData("colnumber", cast(void*)cast(int)index); auto col = new TreeViewColumn(s, cr, "text", cast(int)index); col.setResizable(true); //col.setReorderable(true); tree.appendColumn(col); } //Fill database TreeIter iter = new TreeIter(); for(int i=0 ; i<=twoda.lastLine ; i++){ store.append(iter); store.setValue(iter, 0, i); if(i in twoda.values){ foreach(index, v ; twoda.values[i]){ store.setValue(iter, cast(int)index+1, v); } } else{ foreach(index ; 1..twoda.header.length){ store.setValue(iter, cast(int)index+1, "_"); } } } //Autosize columns tree.columnsAutosize(); tree.setSizeRequest(50, 50); } class TwoDA{ import std.regex; import std.file; this(string filepath){ lastLine = 0; foreach(index, line ; readText(filepath).splitLines()){ string data[]; auto results = matchAll(line, rgxField); foreach(res ; results){ string s; if(res[0][0]=='"') data~= res[2]; else data~= res[1]; } if(index==0){ header = data; //writeln(header); } else{ int nLine = data[0].to!int; values[nLine] = data[1..$]; //writeln(values[nLine]); if(nLine > lastLine) lastLine = nLine; } } } string[] header; string[][uint] values; uint lastLine; enum rgxField = ctRegex!"(?:\\b([^\\s]+?)\\b|\"([^\"]+?)\")"; }