Jelajahi Sumber

more cdb integration: cursor, list toggle, search filter

Nicolas Cannasse 7 tahun lalu
induk
melakukan
381aa75ddd

+ 48 - 30
bin/cdb.css

@@ -1,8 +1,12 @@
+.cdb {
+  background-color: black;
+}
 .cdb .cdb-sheet {
   width: 100%;
   table-layout: fixed;
   border-collapse: collapse;
   border-spacing: 0;
+  background-color: #222;
 }
 .cdb .cdb-sheet th.start {
   width: 30px;
@@ -14,9 +18,6 @@
   background-color: #333;
   border-left: none;
 }
-.cdb .cdb-sheet tr.cursorLine td.start {
-  background-color: #444;
-}
 .cdb .cdb-sheet tr.head td.start {
   cursor: nw-resize;
 }
@@ -36,10 +37,21 @@
   overflow: hidden;
 }
 .cdb .cdb-sheet td.cursor {
-  outline: 1px solid #555;
+  outline: 1px solid #ccc;
 }
 .cdb .cdb-sheet tr.cursorLine {
-  background-color: #EFF1F8;
+  background-color: #262d35;
+}
+.cdb .cdb-sheet tr.cursorLine td.start {
+  background-color: #313a48;
+}
+.cdb .cdb-sheet tr.selected,
+.cdb .cdb-sheet td.selected {
+  background-color: #313a48;
+}
+.cdb .cdb-sheet tr.selected td.start,
+.cdb .cdb-sheet td.selected td.start {
+  background-color: #313a48;
 }
 .cdb .cdb-sheet tr.filtered {
   display: none;
@@ -56,7 +68,7 @@
   text-align: center;
   border-bottom: 1px solid #666;
   border-left: 1px solid #555;
-  background-color: #333;
+  background-color: #222;
   color: #ddd;
   width: auto;
   white-space: nowrap;
@@ -65,10 +77,6 @@
 .cdb .cdb-sheet tr.head .display {
   font-style: italic;
 }
-.cdb .cdb-sheet tr.selected,
-.cdb .cdb-sheet td.selected {
-  background-color: #353535;
-}
 .cdb .cdb-sheet td {
   user-select: none;
   border-left: 1px solid #444;
@@ -77,30 +85,12 @@
   border-bottom: 1px solid #333;
 }
 .cdb .cdb-sheet tr.list {
-  background-color: #CCC;
-  border-bottom: 0px;
-}
-.cdb .cdb-sheet tr.list tr.head {
-  color: #fff;
-  background-color: #777;
-  font-weight: normal;
-  height: 8px;
-}
-.cdb .cdb-sheet tr.list tr {
-  background-color: #EEE;
-  border-color: #E0E0E0;
-}
-.cdb .cdb-sheet tr.list input,
-.cdb .cdb-sheet tr.list select {
-  background-color: #EEE;
-}
-.cdb .cdb-sheet tr.list tr.selected,
-.cdb .cdb-sheet tr.list td.selected {
-  background-color: #353535;
+  background-color: black;
 }
 .cdb .cdb-sheet tr.list > td {
   padding: 0;
   padding-bottom: 5px;
+  border: none;
 }
 .cdb .cdb-sheet th.t_tilepos {
   width: 64px;
@@ -206,3 +196,31 @@
 .cdb .cdb-sheet td.t_file .preview:hover .previewContent {
   display: block;
 }
+.cdb .searchBox {
+  display: none;
+  width: 200px;
+  position: fixed;
+  top: 20px;
+  right: 0px;
+  background-color: black;
+  border: 1px solid #555;
+  border-top: none;
+  border-right: none;
+  border-bottom-left-radius: 5px;
+  padding: 5px;
+  color: #444;
+}
+.cdb .searchBox input {
+  width: 170px;
+  background-color: black;
+}
+.cdb .searchBox i {
+  cursor: pointer;
+  margin-left: 5px;
+  display: inline-block;
+  font-size: 18px;
+  color: #888;
+}
+.cdb .searchBox i:hover {
+  color: #EEE;
+}

+ 48 - 31
bin/cdb.less

@@ -1,12 +1,15 @@
 
 .cdb {
 
+	background-color : black;
+
 	.cdb-sheet {
 
 		width : 100%;
 		table-layout:fixed;
 		border-collapse : collapse;
 		border-spacing : 0;
+		background-color: #222;
 
 		th.start {
 			width : 30px;
@@ -18,9 +21,6 @@
 			background-color: #333;
 			border-left: none;
 		}
-		tr.cursorLine td.start {
-			background-color: #444;
-		}
 
 		tr.head td.start {
 			cursor : nw-resize;
@@ -30,7 +30,6 @@
 			background-color : #444;
 			height : 10px;
 			td {
-//				border-top: 1px solid #99A6D2;
 				border-left: none;
 				padding-top: 6px;
 				color : #bbb;
@@ -45,11 +44,21 @@
 		}
 
 		td.cursor {
-			outline : 1px solid #555;
+			outline : 1px solid #ccc;
 		}
 
 		tr.cursorLine {
-			background-color: #EFF1F8;
+			background-color: #262d35;
+			td.start {
+				background-color: #313a48;
+			}
+		}
+
+		tr.selected, td.selected {
+			background-color : #313a48;
+			td.start {
+				background-color: #313a48;
+			}
 		}
 
 		tr.filtered {
@@ -69,7 +78,7 @@
 			text-align: center;
 			border-bottom : 1px solid #666;
 			border-left: 1px solid #555;
-			background-color : #333;
+			background-color : #222;
 			color : #ddd;
 			width: auto;
 			white-space: nowrap;
@@ -80,10 +89,6 @@
 			font-style : italic;
 		}
 
-		tr.selected, td.selected {
-			background-color : #353535;
-		}
-
 		td {
 			user-select: none;
 			border-left : 1px solid #444;
@@ -96,29 +101,11 @@
 		// ----- types -------
 
 		tr.list {
-			background-color : #CCC;
-			border-bottom : 0px;
-			tr.head {
-				color : #fff;
-				background-color : #777;
-				font-weight : normal;
-				height : 8px;
-			}
-			tr {
-				background-color : #EEE;
-				border-color : #E0E0E0;
-			}
-			input, select {
-				background-color : #EEE;
-			}
-
-			tr.selected, td.selected {
-				background-color : #353535;
-			}
-
+			background-color:black;
 			& > td {
 				padding : 0;
 				padding-bottom: 5px;
+				border : none;
 			}
 		}
 
@@ -242,4 +229,34 @@
 
 	}
 
+	.searchBox {
+		display : none;
+		width : 200px;
+		position : fixed;
+		top : 20px;
+		right : 0px;
+		background-color : black;
+		border : 1px solid #555;
+		border-top : none;
+		border-right : none;
+		border-bottom-left-radius : 5px;
+		padding : 5px;
+		input {
+			width : 170px;
+			background-color : black;
+		}
+		i {
+			cursor : pointer;
+			margin-left : 5px;
+			display : inline-block;
+			font-size : 18px;
+			color : #888;
+		}
+		i:hover {
+			color : #EEE;
+		}
+		color : #444;
+	}
+
+
 }

+ 6 - 0
bin/defaultProps.json

@@ -12,6 +12,12 @@
 	"key.redo" : "Ctrl-Y",
 	"key.save" : "Ctrl-S",
 
+	"key.copy" : "Ctrl-C",
+	"key.cut" : "Ctrl-X",
+	"key.paste" : "Ctrl-V",
+
+	"key.search" : "Ctrl-F",
+
 	// paths to look files/shaders into - relative to project root
 	"haxe.classPath" : ["src"],
 

+ 16 - 11
hide/comp/cdb/Cell.hx

@@ -6,30 +6,34 @@ class Cell extends Component {
 	static var typeNames = [for( t in Type.getEnumConstructs(cdb.Data.ColumnType) ) t.substr(1).toLowerCase()];
 
 	var editor : Editor;
-	var line : Line;
-	var col : cdb.Data.Column;
 	var currentValue : Dynamic;
+	public var line(default,null) : Line;
+	public var column(default, null) : cdb.Data.Column;
+	public var columnIndex(get, never) : Int;
 	public var value(get, set) : Dynamic;
+	public var table(get, never) : Table;
 
-	public function new( root : Element, line : Line, col : cdb.Data.Column ) {
+	public function new( root : Element, line : Line, column : cdb.Data.Column ) {
 		super(root);
 		this.line = line;
 		this.editor = line.table.editor;
-		this.col = col;
+		this.column = column;
 		@:privateAccess line.cells.push(this);
-		currentValue = Reflect.field(line.obj, col.name);
-		root.addClass("t_" + typeNames[col.type.getIndex()]);
+		root.addClass("t_" + typeNames[column.type.getIndex()]);
 		refresh();
 	}
 
+	function get_table() return line.table;
+	function get_columnIndex() return table.sheet.columns.indexOf(column);
+
 	function set_value( v : Dynamic ) {
 		var old = currentValue;
 		currentValue = v;
 		if( v != currentValue ) {
 			if( v == null )
-				Reflect.deleteField(line.obj, col.name);
+				Reflect.deleteField(line.obj, column.name);
 			else
-				Reflect.setField(line.obj, col.name, v);
+				Reflect.setField(line.obj, column.name, v);
 			// TODO : history
 		}
 		refresh();
@@ -38,14 +42,15 @@ class Cell extends Component {
 
 	inline function get_value() return currentValue;
 
-	function refresh() {
-		var html = valueHtml(col, value, editor.sheet, line.obj);
+	public function refresh() {
+		currentValue = Reflect.field(line.obj, column.name);
+		var html = valueHtml(column, value, editor.sheet, line.obj);
 		if( html == "&nbsp;" ) root.text(" ") else if( html.indexOf('<') < 0 && html.indexOf('&') < 0 ) root.text(html) else root.html(html);
 		updateClasses();
 	}
 
 	function updateClasses() {
-		switch( col.type ) {
+		switch( column.type ) {
 		case TBool :
 			root.removeClass("true false").addClass( value==true ? "true" : "false" );
 		case TInt, TFloat :

+ 27 - 19
hide/comp/cdb/Cursor.hx

@@ -3,7 +3,7 @@ package hide.comp.cdb;
 class Cursor {
 
 	var editor : Editor;
-	public var sheet : cdb.Sheet;
+	public var table : Table;
 	public var x : Int;
 	public var y : Int;
 	public var select : Null<{ x : Int, y : Int }>;
@@ -13,8 +13,8 @@ class Cursor {
 		this.editor = editor;
 	}
 
-	public function set( ?s, ?x=0, ?y=0, ?sel, update = true ) {
-		this.sheet = s;
+	public function set( ?t, ?x=0, ?y=0, ?sel, update = true ) {
+		this.table = t;
 		this.x = x;
 		this.y = y;
 		this.select = sel;
@@ -27,12 +27,12 @@ class Cursor {
 	}
 
 	public function getLine() {
-		if( sheet == null ) return null;
-		return editor.getLine(sheet, y);
+		if( table == null ) return null;
+		return table.lines[y];
 	}
 
 	public function move( dx : Int, dy : Int, shift : Bool, ctrl : Bool ) {
-		if( sheet == null )
+		if( table == null )
 			return;
 		if( x == -1 && ctrl ) {
 			if( dy != 0 )
@@ -44,9 +44,9 @@ class Cursor {
 			x--;
 		if( dy < 0 && y > 0 )
 			y--;
-		if( dx > 0 && x < sheet.columns.length - 1 )
+		if( dx > 0 && x < table.sheet.columns.length - 1 )
 			x++;
-		if( dy > 0 && y < sheet.lines.length - 1 )
+		if( dy > 0 && y < table.sheet.lines.length - 1 )
 			y++;
 		select = null;
 		update();
@@ -57,17 +57,17 @@ class Cursor {
 		root.find(".selected").removeClass("selected");
 		root.find(".cursor").removeClass("cursor");
 		root.find(".cursorLine").removeClass("cursorLine");
-		if( sheet == null )
+		if( table == null )
 			return;
 		if( y < 0 ) {
 			y = 0;
 			select = null;
 		}
-		if( y >= sheet.lines.length ) {
-			y = sheet.lines.length - 1;
+		if( y >= table.sheet.lines.length ) {
+			y = table.sheet.lines.length - 1;
 			select = null;
 		}
-		var max = sheet.props.isProps ? 1 : sheet.columns.length;
+		var max = table.sheet.props.isProps ? 1 : table.sheet.columns.length;
 		if( x >= max ) {
 			x = max - 1;
 			select = null;
@@ -79,7 +79,7 @@ class Cursor {
 				var cy = y;
 				while( select.y != cy ) {
 					if( select.y > cy ) cy++ else cy--;
-					editor.getLine(sheet, cy).root.addClass("selected");
+					table.lines[cy].root.addClass("selected");
 				}
 			}
 		} else {
@@ -87,7 +87,7 @@ class Cursor {
 			if( select != null ) {
 				var s = getSelection();
 				for( y in s.y1...s.y2 + 1 ) {
-					var l = editor.getLine(sheet, y);
+					var l = table.lines[y];
 					for( x in s.x1...s.x2+1)
 						l.cells[x].root.addClass("selected");
 				}
@@ -97,11 +97,11 @@ class Cursor {
 		if( e != null ) untyped e.scrollIntoViewIfNeeded();
 	}
 
-	function getSelection() {
-		if( sheet == null )
+	public function getSelection() {
+		if( table == null )
 			return null;
 		var x1 = if( x < 0 ) 0 else x;
-		var x2 = if( x < 0 ) sheet.columns.length-1 else if( select != null ) select.x else x1;
+		var x2 = if( x < 0 ) table.sheet.columns.length-1 else if( select != null ) select.x else x1;
 		var y1 = y;
 		var y2 = if( select != null ) select.y else y1;
 		if( x2 < x1 ) {
@@ -120,11 +120,19 @@ class Cursor {
 
 	public function clickLine( line : Line, shiftKey = false ) {
 		var sheet = line.table.sheet;
-		if( shiftKey && this.sheet == sheet && x < 0 ) {
+		if( shiftKey && this.table == line.table && x < 0 ) {
 			select = { x : -1, y : line.index };
 			update();
 		} else
-			set(sheet, -1, line.index);
+			set(line.table, -1, line.index);
+	}
+
+	public function clickCell( cell : Cell, shiftKey = false ) {
+		if( shiftKey && table == cell.table ) {
+			select = { x : cell.columnIndex, y : cell.line.index };
+			update();
+		} else
+			set(cell.table, cell.columnIndex, cell.line.index);
 	}
 
 }

+ 117 - 1
hide/comp/cdb/Editor.hx

@@ -1,4 +1,5 @@
 package hide.comp.cdb;
+import hxd.Key in K;
 
 @:allow(hide.comp.cdb)
 class Editor extends Component {
@@ -7,21 +8,112 @@ class Editor extends Component {
 	var sheet : cdb.Sheet;
 	var existsCache : Map<String,{ t : Float, r : Bool }> = new Map();
 	var tables : Array<Table> = [];
+	var keys : hide.ui.Keys;
+	var searchBox : Element;
+	var clipboard : {
+		text : String,
+		data : {},
+		schema : Array<cdb.Data.Column>,
+	};
 	public var cursor : Cursor;
 
-	public function new(root, sheet) {
+	public function new(root, sheet, keys) {
 		super(root);
 		this.sheet = sheet;
+		this.keys = keys;
+		keys.addListener(onKey);
+		keys.register("search", function() {
+			searchBox.show();
+			searchBox.find("input").focus().select();
+		});
+		keys.register("copy", onCopy);
 		base = sheet.base;
 		cursor = new Cursor(this);
 		refresh();
 	}
 
+	function onKey( e : js.jquery.Event ) {
+		switch( e.keyCode ) {
+		case K.LEFT:
+			cursor.move( -1, 0, e.shiftKey, e.ctrlKey);
+			return true;
+		case K.RIGHT:
+			cursor.move( 1, 0, e.shiftKey, e.ctrlKey);
+			return true;
+		case K.UP:
+			cursor.move( 0, -1, e.shiftKey, e.ctrlKey);
+			return true;
+		case K.DOWN:
+			cursor.move( 0, 1, e.shiftKey, e.ctrlKey);
+			return true;
+		case K.SPACE:
+			e.preventDefault(); // prevent scroll
+		}
+		return false;
+	}
+
+	function searchFilter( filter : String ) {
+
+		if( filter == "" ) filter = null;
+		if( filter != null ) filter = filter.toLowerCase();
+
+		var lines = root.find("table.cdb-sheet > tr").not(".head");
+		lines.removeClass("filtered");
+		if( filter != null ) {
+			for( t in lines ) {
+				if( t.textContent.toLowerCase().indexOf(filter) < 0 )
+					t.classList.add("filtered");
+			}
+			while( lines.length > 0 ) {
+				lines = lines.filter(".list").not(".filtered").prev();
+				lines.removeClass("filtered");
+			}
+		}
+	}
+
+	function onCopy() {
+		var sel = cursor.getSelection();
+		if( sel == null )
+			return;
+		var data = [];
+		for( y in sel.y1...sel.y2+1 ) {
+			var obj = cursor.table.lines[y].obj;
+			var out = {};
+			for( x in sel.x1...sel.x2+1 ) {
+				var c = cursor.table.sheet.columns[x];
+				var v = Reflect.field(obj, c.name);
+				if( v != null )
+					Reflect.setField(out, c.name, v);
+			}
+			data.push(out);
+		}
+		clipboard = {
+			data : data,
+			text : Std.string([for( o in data ) cursor.table.sheet.objToString(o,true)]),
+			schema : [for( x in sel.x1...sel.x2+1 ) cursor.table.sheet.columns[x]],
+		};
+		ide.setClipboard(clipboard.text);
+	}
+
 	function refresh() {
 
 		root.html('');
 		root.addClass('cdb');
 
+		searchBox = new Element("<div>").addClass("searchBox").appendTo(root);
+		new Element("<input type='text'>").appendTo(searchBox).keydown(function(e) {
+			if( e.keyCode == 27 ) {
+				searchBox.find("i").click();
+				return;
+			}
+		}).keyup(function(e) {
+			searchFilter(e.getThis().val());
+		});
+		new Element("<i>").addClass("fa fa-times-circle").appendTo(searchBox).click(function(_) {
+			searchFilter(null);
+			searchBox.toggle();
+		});
+
 		if( sheet.columns.length == 0 ) {
 			new Element("<a>Add a column</a>").appendTo(root).click(function(_) {
 				newColumn(sheet);
@@ -33,6 +125,7 @@ class Editor extends Component {
 		tables = [];
 		new Table(this, sheet, content);
 		content.appendTo(root);
+
 	}
 
 	function quickExists(path) {
@@ -62,6 +155,9 @@ class Editor extends Component {
 	public function insertLine( table : Table, index = 0 ) {
 	}
 
+	public function popupColumn( table : Table, col : cdb.Data.Column ) {
+	}
+
 	function moveLine( line : Line, delta : Int ) {
 		/*
 		// remove opened list
@@ -78,6 +174,26 @@ class Editor extends Component {
 	public function popupLine( line : Line ) {
 	}
 
+	public function makeSubSheet( cell : Cell ) {
+		var sheet = cell.table.sheet;
+		var c = cell.column;
+		var index = cell.line.index;
+		var key = sheet.getPath() + "@" + c.name + ":" + index;
+		var psheet = sheet.getSub(c);
+		return new cdb.Sheet(base,{
+			columns : psheet.columns, // SHARE
+			props : psheet.props, // SHARE
+			name : psheet.name, // same
+			lines : cell.value, // ref
+			separators : [], // none
+		},key, { sheet : sheet, column : cell.columnIndex, line : index });
+	}
+
+	public function close() {
+		for( t in tables.copy() )
+			t.dispose();
+	}
+
 	public dynamic function save() {
 	}
 

+ 1 - 0
hide/comp/cdb/Line.hx

@@ -6,6 +6,7 @@ class Line extends Component {
 	public var table : Table;
 	public var obj(get, never) : Dynamic;
 	public var cells : Array<Cell>;
+	public var subTable : SubTable;
 
 	public function new(table, index, root) {
 		super(root);

+ 58 - 0
hide/comp/cdb/SubTable.hx

@@ -0,0 +1,58 @@
+package hide.comp.cdb;
+
+class SubTable extends Table {
+
+	var insertedTR : Element;
+	var slider : Element;
+	public var cell : Cell;
+
+	public function new(editor, sheet:cdb.Sheet, cell:Cell) {
+
+		this.cell = cell;
+		if( sheet.lines == null ) {
+			@:privateAccess sheet.sheet.lines = [];
+			Reflect.setField(cell.line.obj, cell.column.name, sheet.lines);
+			// do not save for now
+		}
+
+		var line = cell.line;
+		if( line.subTable != null ) throw "assert";
+		line.subTable = this;
+
+		insertedTR = new Element("<tr>").addClass("list");
+		new Element("<td>").appendTo(insertedTR);
+		var group = new Element("<td>").attr("colspan", "" + cell.table.sheet.columns.length).appendTo(insertedTR);
+		slider = new Element("<div>").appendTo(group);
+		slider.hide();
+		var root = new Element("<table>");
+		root.appendTo(slider);
+
+		insertedTR.insertAfter(cell.line.root);
+		cell.root.text("...");
+
+		super(editor, sheet, root);
+	}
+
+	public function show() {
+		slider.slideDown(100);
+	}
+
+	override public function close() {
+		if( slider != null ) {
+			slider.slideUp(100, function() { slider = null; close(); });
+			return;
+		}
+		super.close();
+	}
+
+	override function dispose() {
+		super.dispose();
+		insertedTR.remove();
+		if( cell.column.opt && sheet.lines.length == 0 ) {
+			Reflect.deleteField(cell.line.obj, cell.column.name);
+			editor.save();
+		}
+		cell.refresh();
+	}
+
+}

+ 27 - 4
hide/comp/cdb/Table.hx

@@ -16,8 +16,13 @@ class Table extends Component {
 		refresh();
 	}
 
+	public function close() {
+		root.remove();
+		dispose();
+	}
+
 	public function dispose() {
-		@:privateAccess editor.tables.remove(this);
+		editor.tables.remove(this);
 	}
 
 	public function refresh() {
@@ -55,7 +60,7 @@ class Table extends Component {
 				col.addClass("display");
 			col.mousedown(function(e) {
 				if( e.which == 3 ) {
-					//haxe.Timer.delay(popupColumn.bind(sheet,c),1);
+					editor.popupColumn(this, c);
 					e.preventDefault();
 					return;
 				}
@@ -70,6 +75,12 @@ class Table extends Component {
 				var line = lines[index];
 				v.appendTo(line.root);
 				var cell = new Cell(v, line, c);
+
+				v.click(function(e) {
+					editor.cursor.clickCell(cell, e.shiftKey);
+					e.stopPropagation();
+				});
+
 				switch( c.type ) {
 				case TList:
 					var key = sheet.getPath() + "@" + c.name + ":" + index;
@@ -96,9 +107,10 @@ class Table extends Component {
 				sep.dblclick(function(e) {
 					content.empty();
 					J("<input>").appendTo(content).focus().val(title == null ? "" : title).blur(function(_) {
-						/*title = JTHIS.val();
+						title = JTHIS.val();
 						JTHIS.remove();
 						content.text(title);
+						/*
 						var titles = sheet.props.separatorTitles;
 						if( titles == null ) titles = [];
 						while( titles.length < pos )
@@ -125,7 +137,7 @@ class Table extends Component {
 			var l = J('<tr><td colspan="${sheet.columns.length + 1}"><a href="javascript:_.insertLine()">Insert Line</a></td></tr>');
 			l.find("a").click(function(_) {
 				editor.insertLine(this);
-				editor.cursor.set(sheet);
+				editor.cursor.set(this);
 			});
 			root.append(l);
 		}
@@ -136,6 +148,17 @@ class Table extends Component {
 	}
 
 	function toggleList( cell : Cell ) {
+		var line = cell.line;
+		var cur = line.subTable;
+		if( cur != null ) {
+			cur.close();
+			line.subTable = null;
+			if( cur.cell == cell ) return; // toggle
+		}
+		var sheet = editor.makeSubSheet(cell);
+		var sub = new SubTable(editor, sheet, cell);
+		sub.show();
+		editor.cursor.set(sub);
 	}
 
 }

+ 4 - 0
hide/ui/Ide.hx

@@ -215,6 +215,10 @@ class Ide {
 	function get_ideProps() return props.global.source.hide;
 	function get_currentProps() return props.user;
 
+	public function setClipboard( text : String ) {
+		nw.Clipboard.get().set(text, Text);
+	}
+
 	public function registerUpdate( updateFun ) {
 		updates.push(updateFun);
 	}

+ 29 - 17
hide/ui/Keys.hx

@@ -4,11 +4,18 @@ class Keys {
 
 	var config : Props;
 	var keys = new Map<String,Void->Void>();
+	var listeners = new Array<js.jquery.Event -> Bool>();
+	// allow a sub set to hierarchise and prevent leaks wrt refresh
+	public var subKeys : Array<Keys> = [];
 
 	public function new( config : Props ) {
 		this.config = config;
 	}
 
+	public function addListener( l ) {
+		listeners.push(l);
+	}
+
 	public function processEvent( e : js.jquery.Event ) {
 		var active = js.Browser.document.activeElement;
 		if( active != null && active.nodeName == "INPUT" ) return;
@@ -20,33 +27,38 @@ class Keys {
 			parts.push("Ctrl");
 		if( e.shiftKey )
 			parts.push("Shift");
-		if( e.keyCode >= 'A'.code && e.keyCode <= 'Z'.code )
-			parts.push(String.fromCharCode(e.keyCode));
-		else if( e.keyCode >= 96 && e.keyCode <= 105 )
-			parts.push(String.fromCharCode('0'.code + e.keyCode - 96));
-		else if( e.keyCode == ' '.code )
-			parts.push("Space");
-		else if( e.keyCode == 13 )
-			parts.push("Enter");
-		else if( e.keyCode == 27 )
-			parts.push("Esc");
-		else if( e.keyCode == 16 || e.keyCode == 17 || e.keyCode == 18 ) {
-			// alt-ctrl-shift
+		if( e.keyCode == hxd.Key.ALT || e.keyCode == hxd.Key.SHIFT || e.keyCode == hxd.Key.CTRL ) {
+			//
 		} else {
-			//trace(e.key + "=" + e.keyCode+" (" + String.fromCharCode(e.keyCode) + ")");
-			if( e.key != "" )
+			var name = hxd.Key.getKeyName(e.keyCode);
+			if( name != null )
+				parts.push(name);
+			else if( e.key != "" )
 				parts.push(e.key);
 			else
-				return;
+				parts.push(""+e.keyCode);
 		}
 
 		var key = parts.join("-");
+		if( triggerKey(e, key) ) {
+			e.stopPropagation();
+			e.preventDefault();
+		}
+	}
+
+	public function triggerKey( e : js.jquery.Event, key : String ) {
+		for( s in subKeys )
+			if( s.triggerKey(e, key) )
+				return true;
+		for( l in listeners )
+			if( l(e) )
+				return true;
 		var callb = keys.get(key);
 		if( callb != null ) {
 			callb();
-			e.stopPropagation();
-			e.preventDefault();
+			return true;
 		}
+		return false;
 	}
 
 	public function register( name : String, callb : Void -> Void ) {

+ 4 - 1
hide/view/CdbTable.hx

@@ -20,7 +20,10 @@ class CdbTable extends hide.ui.View<{ path : String }> {
 			return;
 		}
 		root.addClass("hide-scroll");
-		editor = new hide.comp.cdb.Editor(root, sheet);
+		var keys = new hide.ui.Keys(props);
+		this.keys.subKeys = [keys];
+		editor = new hide.comp.cdb.Editor(root, sheet, keys);
+		new Element("<div style='width:100%; height:300px'></div>").appendTo(root);
 	}
 
 	override function getTitle() {