2014-11-16 23:48:56 +01:00
|
|
|
import std.stdio;
|
2014-11-18 11:46:07 +01:00
|
|
|
import std.file;
|
2014-11-16 23:48:56 +01:00
|
|
|
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;
|
2014-11-17 23:25:13 +01:00
|
|
|
import gtk.VBox;
|
2014-11-18 14:06:26 +01:00
|
|
|
import gtk.Statusbar;
|
2014-11-17 15:12:57 +01:00
|
|
|
import gtk.Button;
|
2014-11-18 14:53:05 +01:00
|
|
|
import gtk.ScrolledWindow;
|
2014-11-18 11:46:07 +01:00
|
|
|
|
2014-11-18 14:06:26 +01:00
|
|
|
import gtk.HBox;
|
2014-11-18 11:46:07 +01:00
|
|
|
|
2014-11-18 17:06:38 +01:00
|
|
|
MainWindow window;
|
|
|
|
|
2014-11-16 23:48:56 +01:00
|
|
|
void main(string[] args)
|
|
|
|
{
|
|
|
|
Main.init(args);
|
|
|
|
|
2014-11-17 15:49:53 +01:00
|
|
|
//Window
|
2014-11-18 17:06:38 +01:00
|
|
|
window = new MainWindow("2DA-Edit");
|
2014-11-16 23:48:56 +01:00
|
|
|
|
2014-11-17 23:25:13 +01:00
|
|
|
auto cont = new VBox(false, 0);
|
|
|
|
window.add(cont);
|
2014-11-18 15:20:39 +01:00
|
|
|
cont.setSizeRequest(300, 200);
|
2014-11-17 23:25:13 +01:00
|
|
|
|
2014-11-18 18:20:56 +01:00
|
|
|
version(Windows){
|
|
|
|
auto buttonSave = new Button(StockID.SAVE, true);
|
|
|
|
auto buttonOpen = new Button(StockID.OPEN, true);
|
|
|
|
auto buttonInsert = new Button(StockID.JUMP_TO, true);
|
|
|
|
auto buttonDelete = new Button(StockID.DELETE, true);
|
|
|
|
auto buttonRenumber = new Button(StockID.INDEX, true);
|
|
|
|
auto buttonNewCol = new Button(StockID.NEW, true);
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
auto buttonSave = new Button("document-save-symbolic", GtkIconSize.MENU);
|
|
|
|
auto buttonOpen = new Button("document-open-symbolic", GtkIconSize.MENU);
|
|
|
|
auto buttonInsert = new Button("format-text-direction-ltr-symbolic", GtkIconSize.SMALL_TOOLBAR);
|
|
|
|
auto buttonDelete = new Button("user-trash-symbolic", GtkIconSize.SMALL_TOOLBAR);
|
|
|
|
auto buttonRenumber = new Button("view-list-symbolic", GtkIconSize.SMALL_TOOLBAR);
|
|
|
|
auto buttonNewCol = new Button("tab-new-symbolic", GtkIconSize.SMALL_TOOLBAR);
|
|
|
|
}
|
2014-11-18 14:53:05 +01:00
|
|
|
buttonSave.setTooltipText("Save");
|
|
|
|
buttonOpen.setTooltipText("Open 2DA");
|
|
|
|
buttonInsert.setTooltipText("Insert row after");
|
|
|
|
buttonDelete.setTooltipText("Delete row");
|
|
|
|
buttonRenumber.setTooltipText("Renumber all rows");
|
2014-11-18 17:06:38 +01:00
|
|
|
buttonNewCol.setTooltipText("Add new column");
|
2014-11-18 11:46:07 +01:00
|
|
|
|
2014-11-17 23:25:13 +01:00
|
|
|
version(Windows){
|
|
|
|
//Menu bar
|
|
|
|
import gtk.HBox;
|
|
|
|
|
|
|
|
auto cont2 = new HBox(false, 0);
|
|
|
|
cont.packStart(cont2, false, false, 0);
|
|
|
|
|
2014-11-18 11:46:07 +01:00
|
|
|
cont2.packStart(buttonOpen, false, false, 0);
|
|
|
|
cont2.packEnd(buttonSave, false, false, 0);
|
2014-11-17 23:25:13 +01:00
|
|
|
}
|
|
|
|
else{
|
|
|
|
//Header bar
|
|
|
|
import gtk.HeaderBar;
|
|
|
|
|
|
|
|
auto header = new HeaderBar();
|
|
|
|
window.setTitlebar(header);
|
|
|
|
header.setTitle("2DAEdit");
|
|
|
|
header.setProperty("show-close-button", true);
|
2014-11-16 23:48:56 +01:00
|
|
|
|
2014-11-18 11:46:07 +01:00
|
|
|
header.packStart(buttonOpen);
|
|
|
|
header.packEnd(buttonSave);
|
|
|
|
}
|
2014-11-16 23:48:56 +01:00
|
|
|
|
2014-11-18 14:06:26 +01:00
|
|
|
//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);
|
2014-11-18 17:06:38 +01:00
|
|
|
statusbar.packEnd(buttonNewCol, false, false, 5);
|
2014-11-18 14:06:26 +01:00
|
|
|
|
2014-11-17 15:49:53 +01:00
|
|
|
//TreeView to display database
|
2014-11-18 14:53:05 +01:00
|
|
|
auto scroll = new ScrolledWindow(PolicyType.AUTOMATIC, PolicyType.AUTOMATIC);
|
|
|
|
cont.packEnd(scroll, true, true, 0);
|
|
|
|
|
2014-11-18 11:46:07 +01:00
|
|
|
auto tree = new TreeView();
|
2014-11-18 14:53:05 +01:00
|
|
|
scroll.add(tree);
|
2014-11-17 15:49:53 +01:00
|
|
|
|
2014-11-16 23:48:56 +01:00
|
|
|
tree.setHeadersVisible(true);
|
|
|
|
tree.setEnableSearch(true);
|
2014-11-17 11:50:11 +01:00
|
|
|
tree.setProperty("enable-grid-lines", GtkTreeViewGridLines.VERTICAL);
|
2014-11-17 13:52:50 +01:00
|
|
|
tree.setProperty("tooltip-column", 0);
|
|
|
|
tree.setProperty("reorderable", true);
|
|
|
|
tree.setProperty("headers-clickable", true);
|
|
|
|
|
2014-11-18 11:46:07 +01:00
|
|
|
|
|
|
|
//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();
|
2014-11-18 17:06:38 +01:00
|
|
|
version(Windows) Open(filename, tree, cast(Object)window);
|
|
|
|
else Open(filename, tree, cast(Object)header);
|
2014-11-18 11:46:07 +01:00
|
|
|
}
|
|
|
|
fc.destroy();
|
|
|
|
});
|
|
|
|
|
2014-11-18 14:41:47 +01:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2014-11-18 17:06:38 +01:00
|
|
|
buttonNewCol.addOnClicked((Button){
|
|
|
|
auto store = cast(ListStore)tree.getModel();
|
|
|
|
int newColIndex = store.getNColumns();
|
|
|
|
|
|
|
|
|
|
|
|
GType[] types;
|
|
|
|
foreach(i ; 0..newColIndex+1)
|
|
|
|
types~= store.getColumnType(i);
|
|
|
|
store.setColumnTypes(types);
|
|
|
|
|
|
|
|
writeln(types.length);
|
|
|
|
|
|
|
|
|
|
|
|
//auto col = SetupColumn(tree, "new_col", newColIndex);
|
|
|
|
|
|
|
|
|
|
|
|
//TreeIter it = new TreeIter();
|
|
|
|
//if(store.getIterFirst(it)){
|
|
|
|
// do{
|
|
|
|
// store.setValue(it, newColIndex, "_");
|
|
|
|
// }while(store.iterNext(it));
|
|
|
|
//}
|
|
|
|
|
|
|
|
//tree.appendColumn(col);
|
|
|
|
});
|
|
|
|
|
2014-11-18 14:41:47 +01:00
|
|
|
|
|
|
|
|
2014-11-18 11:46:07 +01:00
|
|
|
//Open if exists
|
2014-11-18 12:44:30 +01:00
|
|
|
if(args.length>=2 && exists(args[1])){
|
2014-11-18 17:06:38 +01:00
|
|
|
version(Windows) Open(args[1], tree, cast(Object)window);
|
|
|
|
else Open(args[1], tree, cast(Object)header);
|
2014-11-18 11:46:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
window.showAll();
|
|
|
|
|
|
|
|
Main.run();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void Save(ref TreeView tree){
|
|
|
|
auto store = cast(ListStore)tree.getModel();
|
|
|
|
if(store !is null){
|
2014-11-18 15:20:39 +01:00
|
|
|
|
|
|
|
auto file = File(openedFile, "w");
|
|
|
|
|
|
|
|
foreach(i ; 0..tree.getNColumns)
|
|
|
|
file.write(tree.getColumn(i).getTitle, "\t");
|
|
|
|
file.write("\n");
|
|
|
|
|
2014-11-18 11:46:07 +01:00
|
|
|
TreeIter it = new TreeIter();
|
2014-11-18 14:41:47 +01:00
|
|
|
if(store.getIterFirst(it)){
|
|
|
|
do{
|
2014-11-18 15:20:39 +01:00
|
|
|
file.write(store.getValueInt(it, 0));
|
2014-11-18 14:41:47 +01:00
|
|
|
foreach(i ; 1..store.getNColumns()){
|
2014-11-18 15:20:39 +01:00
|
|
|
file.write("\t\"",store.getValueString(it, i),"\"");
|
2014-11-18 14:41:47 +01:00
|
|
|
}
|
2014-11-18 15:20:39 +01:00
|
|
|
file.write("\n");
|
2014-11-18 14:41:47 +01:00
|
|
|
}while(store.iterNext(it));
|
2014-11-18 15:20:39 +01:00
|
|
|
|
|
|
|
file.flush();
|
|
|
|
file.close();
|
|
|
|
writeln("File written: ",openedFile);
|
2014-11-18 14:41:47 +01:00
|
|
|
}
|
2014-11-18 11:46:07 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
writeln("Nothing to save !");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2014-11-18 15:20:39 +01:00
|
|
|
string openedFile;
|
2014-11-18 17:06:38 +01:00
|
|
|
void Open(string file, ref TreeView tree, Object header){
|
2014-11-18 11:46:07 +01:00
|
|
|
|
|
|
|
auto twoda = new TwoDA(file);
|
|
|
|
|
2014-11-18 15:20:39 +01:00
|
|
|
openedFile = file;
|
|
|
|
|
2014-11-18 15:52:53 +01:00
|
|
|
version(Windows) (cast(MainWindow)header).setTitle(file);
|
2014-11-18 12:44:30 +01:00
|
|
|
else{
|
|
|
|
import gtk.HeaderBar;
|
|
|
|
(cast(HeaderBar)header).setSubtitle(file);
|
|
|
|
}
|
|
|
|
|
2014-11-18 11:46:07 +01:00
|
|
|
//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[];
|
2014-11-18 14:23:38 +01:00
|
|
|
type~=GType.INT;
|
|
|
|
foreach(i;1..twoda.header.length)type~=GType.STRING;
|
2014-11-18 11:46:07 +01:00
|
|
|
|
|
|
|
//Create new store
|
|
|
|
auto store = new ListStore(type);
|
|
|
|
tree.setModel(store);
|
|
|
|
|
|
|
|
|
2014-11-17 15:49:53 +01:00
|
|
|
//Setup TreeView columns
|
2014-11-16 23:48:56 +01:00
|
|
|
foreach(index, s ; twoda.header){
|
|
|
|
|
2014-11-18 17:06:38 +01:00
|
|
|
auto col = SetupColumn(tree, s, index);
|
2014-11-16 23:48:56 +01:00
|
|
|
tree.appendColumn(col);
|
|
|
|
}
|
|
|
|
|
2014-11-17 15:49:53 +01:00
|
|
|
//Fill database
|
2014-11-17 11:50:11 +01:00
|
|
|
TreeIter iter = new TreeIter();
|
2014-11-16 23:48:56 +01:00
|
|
|
|
|
|
|
for(int i=0 ; i<=twoda.lastLine ; i++){
|
2014-11-17 11:50:11 +01:00
|
|
|
store.append(iter);
|
2014-11-16 23:48:56 +01:00
|
|
|
store.setValue(iter, 0, i);
|
|
|
|
|
|
|
|
if(i in twoda.values){
|
|
|
|
foreach(index, v ; twoda.values[i]){
|
2014-11-17 11:50:11 +01:00
|
|
|
store.setValue(iter, cast(int)index+1, v);
|
2014-11-16 23:48:56 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
foreach(index ; 1..twoda.header.length){
|
2014-11-17 11:50:11 +01:00
|
|
|
store.setValue(iter, cast(int)index+1, "_");
|
2014-11-16 23:48:56 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-17 15:49:53 +01:00
|
|
|
//Autosize columns
|
2014-11-16 23:48:56 +01:00
|
|
|
tree.columnsAutosize();
|
2014-11-18 14:06:26 +01:00
|
|
|
|
|
|
|
tree.setSizeRequest(50, 50);
|
2014-11-16 23:48:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-11-18 17:06:38 +01:00
|
|
|
//Note: tree is only used on events (click)
|
|
|
|
auto ref SetupColumn(TreeView tree, string sName, size_t index){
|
|
|
|
auto store = cast(ListStore)tree.getModel();
|
|
|
|
|
|
|
|
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(sName, cr, "text", cast(int)index);
|
|
|
|
col.setResizable(true);
|
|
|
|
col.setMinWidth(10);
|
|
|
|
col.setClickable(true);
|
|
|
|
col.addOnClicked((TreeViewColumn col){
|
|
|
|
import gtk.Dialog;
|
|
|
|
auto dlg = new Dialog("Rename column", window, GtkDialogFlags.MODAL, ["Cancel","Rename"], [ResponseType.CANCEL, ResponseType.OK]);
|
|
|
|
auto entry = new Entry(col.getTitle);
|
|
|
|
dlg.getContentArea.packStart(entry, false, false, 5);
|
|
|
|
entry.show();
|
|
|
|
|
|
|
|
if(dlg.run()==ResponseType.OK){
|
|
|
|
auto newname = entry.getText.strip;
|
|
|
|
if(newname.countchars(" \t\n\r")==0)
|
|
|
|
col.setTitle(newname);
|
|
|
|
else
|
|
|
|
writeln("Unauthorized caracters in column name");
|
|
|
|
}
|
|
|
|
|
|
|
|
dlg.destroy();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
return col;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2014-11-16 23:48:56 +01:00
|
|
|
|
|
|
|
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|\"([^\"]+?)\")";
|
|
|
|
}
|
|
|
|
|