package hide.comp.cdb; import hxd.Key in K; class Cell extends Component { static var typeNames = [for( t in Type.getEnumConstructs(cdb.Data.ColumnType) ) t.substr(1).toLowerCase()]; static var imageDims : Map = new Map(); var editor : Editor; var currentValue : Dynamic; var watches : Array = new Array(); public var line(default,null) : Line; public var column(default, null) : cdb.Data.Column; public var columnIndex(get, never) : Int; public var value(get, never) : Dynamic; public var table(get, never) : Table; public function new( root : Element, line : Line, column : cdb.Data.Column ) { super(null,root); this.line = line; this.editor = line.table.editor; this.column = column; @:privateAccess line.cells.push(this); root.addClass("t_" + typeNames[column.type.getIndex()]); root.addClass("n_" + column.name); if(line.table.parent == null) { var editProps = editor.getColumnProps(column); root.toggleClass("cat", editProps.categories != null); if(editProps.categories != null) for(c in editProps.categories) root.addClass("cat-" + c); var visible = editor.isColumnVisible(column); root.toggleClass("hidden", !visible); if(!visible) return; } // Used to get the Cell component back from its DOM/Jquery element root.prop("cellComp", this); if( column.kind == Script ) root.addClass("t_script"); refresh(); switch( column.type ) { case TList, TProperties: element.click(function(e) { if( e.shiftKey ) return; e.stopPropagation(); line.table.toggleList(this); }); case TString if( column.kind == Script ): element.click(function(_) edit()); default: if( canEdit() ) element.dblclick(function(_) edit()); else root.addClass("t_readonly"); } root.click(function(e) { editor.cursor.clickCell(this, e.shiftKey); e.stopPropagation(); }); root.contextmenu(function(e) { showMenu(); e.stopPropagation(); e.preventDefault(); }); } public function dragDropFile( relativePath : String, isDrop : Bool = false ) : Bool { if ( !canEdit() || column.type != TFile) return false; if ( isDrop ) { setValue(relativePath); refresh(); } return true; } function evaluate() { var f = editor.formulas.get(this); if( f == null ) return; var newV : Float = try f.call(line.obj) catch( e : Dynamic ) Math.NaN; if( newV != currentValue ) { currentValue = newV; Reflect.setField(line.obj, column.name, newV); refresh(); } } function showMenu() { var menu : Array = null; switch( column.type ) { case TRef(_): if( value != null && value != "" ) menu = [ { label : "Goto", click : () -> @:privateAccess editor.gotoReference(this), keys : this.editor.config.get("key.cdb.gotoReference"), }, ]; case TInt, TFloat: function setF( f : Formulas.Formula ) { editor.beginChanges(); editor.formulas.set(this, f); line.evaluate(); editor.endChanges(); refresh(); } var forms : Array; var current = editor.formulas.get(this); forms = [for( f in editor.formulas.getList(table.sheet) ) { label : f.name, click : () -> if( f == current ) setF(null) else setF(f), checked : f == current }]; forms.push({ label : "New...", click : () -> editor.formulas.createNew(this, setF) }); menu = [ { label : "Formula", menu : forms } ]; default: } if( menu != null ) { focus(); new ContextMenu(menu); } } public function canEdit() { return table.canEditColumn(column.name); } function get_table() return line.table; function get_columnIndex() return table.columns.indexOf(column); inline function get_value() return currentValue; function getCellConfigValue( name : String, ?def : T ) : T { var cfg = ide.currentConfig; var paths = table.sheet.name.split("@"); paths.unshift("cdb"); paths.push(column.name); while ( paths.length != 0 ) { var config = cfg.get(paths.join("."), null); if ( config != null && Reflect.hasField(config, name) ) return Reflect.field(config, name); paths.pop(); } return def; } static var R_HTML = ~/[<&]/; public function refresh(withSubtable = false) { currentValue = Reflect.field(line.obj, column.name); var html = valueHtml(column, value, line.table.getRealSheet(), line.obj, []); if( html == " " ) element.text(" ") else if( !R_HTML.match(html) ) element.text(html) else element.html(html); updateClasses(); var subTable = line.subTable; if( withSubtable && subTable != null && subTable.cell == this) { if( column.type == TString && column.kind == Script ) subTable.refresh(); else table.refreshList(this); } } function watchFile( file : String ) { if( file == null ) return; if( watches.indexOf(file) != -1 ) return; watches.push(file); ide.fileWatcher.register(file, function() { refresh(); }, true, element); } function updateClasses() { element.removeClass("edit"); element.removeClass("edit_long"); switch( column.type ) { case TBool: element.toggleClass("true", value == true); element.toggleClass("false", value == false); case TInt, TFloat: element.toggleClass("zero", value == 0 ); element.toggleClass("nan", Math.isNaN(value)); element.toggleClass("formula", editor.formulas.has(this) ); default: } } function getSheetView( sheet : cdb.Sheet ) { var view = table.editor.view; if( view == null ) return null; var path = sheet.name.split("@"); var view = view.get(path.shift()); for( name in path ) { var sub = view.sub == null ? null : view.sub.get(name); if( sub == null ) return null; view = sub; } return view; } function canViewSubColumn( sheet : cdb.Sheet, column : String ) { var view = getSheetView(sheet); return view == null || view.show == null || view.show.indexOf(column) >= 0; } var _cachedScope : Array<{ s : cdb.Sheet, obj : Dynamic }>; function getScope() { if( _cachedScope != null ) return _cachedScope; var scope = []; var line = line; while( true ) { var p = Std.downcast(line.table, SubTable); if( p == null ) break; line = p.cell.line; scope.unshift({ s : line.table.getRealSheet(), obj : line.obj }); } return _cachedScope = scope; } function makeId( scopes : Array<{ s : cdb.Sheet, obj : Dynamic }>, scope : Int, id : String ) { var ids = []; if( id != null ) ids.push(id); var pos = scopes.length; while( true ) { pos -= scope; if( pos < 0 ) { scopes = getScope(); pos += scopes.length; } var s = scopes[pos]; var pid = Reflect.field(s.obj, s.s.idCol.name); if( pid == null ) return ""; ids.unshift(pid); scope = s.s.idCol.scope; if( scope == null ) break; } return ids.join(":"); } function refScope( targetSheet : cdb.Sheet, currentSheet : cdb.Sheet, obj : Dynamic, localScope : Array<{ s : cdb.Sheet, obj : Dynamic }> ) { var targetDepth = targetSheet.name.split("@").length; var scope = getScope().concat(localScope); if( scope.length < targetDepth ) scope.push({ s : currentSheet, obj : obj }); while( scope.length >= targetDepth ) scope.pop(); return scope; } function valueHtml( c : cdb.Data.Column, v : Dynamic, sheet : cdb.Sheet, obj : Dynamic, scope : Array<{ s : cdb.Sheet, obj : Dynamic }> ) : String { if( v == null ) { if( c.opt ) return " "; return '#NULL'; } return switch( c.type ) { case TInt, TFloat: switch( c.display ) { case Percent: (Math.round(v * 10000)/100) + "%"; default: v + ""; } case TId: if( v == "" ) '#MISSING'; else { var id = c.scope != null ? makeId(scope,c.scope,v) : v; editor.isUniqueID(sheet,obj,id) ? v : '#DUP($v)'; } case TString if( c.kind == Script ): // wrap content in div because td cannot have max-height v == "" ? " " : '
${colorizeScript(c,v, sheet.idCol == null ? null : Reflect.field(obj, sheet.idCol.name))}
'; case TString, TLayer(_): v == "" ? " " : StringTools.htmlEscape(v).split("\n").join("
"); case TRef(sname): if( v == "" ) '#MISSING'; else { var s = editor.base.getSheet(sname); var i = s.index.get(s.idCol.scope != null ? makeId(refScope(s,sheet,obj,scope),s.idCol.scope,v) : v); i == null ? '#REF($v)' : (i.ico == null ? "" : tileHtml(i.ico,true)+" ") + StringTools.htmlEscape(i.disp); } case TBool: v?"Y":"N"; case TEnum(values): values[v]; case TImage: '#DEPRECATED'; case TList: var a : Array = v; var ps = sheet.getSub(c); var out : Array = []; var size = 0; scope.push({ s : sheet, obj : obj }); for( v in a ) { var vals = []; for( c in ps.columns ) { if( !canViewSubColumn(ps, c.name) ) continue; var h = valueHtml(c, Reflect.field(v, c.name), ps, v, scope); if( h != "" && h != " " ) vals.push(h); } inline function char(s) return '$s'; var v = vals.length == 1 ? vals[0] : (char('[') + vals.join(char(',')) + char(']')); if( size > 200 ) { out.push("..."); break; } var vstr = v; if( v.indexOf("<") >= 0 ) { vstr = ~/]+>/g.replace(vstr, ""); vstr = ~//g.replace(vstr, "[I]"); vstr = ~/
"); case TCustom(name): var t = editor.base.getCustomType(name); var a : Array = v; var cas = t.cases[a[0]]; var str = cas.name; if( cas.args.length > 0 ) { str += "("; var out = []; var pos = 1; for( i in 1...a.length ) out.push(valueHtml(cas.args[i-1], a[i], sheet, this, scope)); str += out.join(","); str += ")"; } str; case TFlags(values): var v : Int = v; var view = getSheetView(sheet); if( view != null && view.options != null ) { var mask = Reflect.field(view.options,c.name); if( mask != null ) v &= mask; } var flags = []; for( i in 0...values.length ) if( v & (1 << i) != 0 ) flags.push(StringTools.htmlEscape(values[i])); flags.length == 0 ? String.fromCharCode(0x2205) : flags.join("|"+String.fromCharCode(0x200B)); case TColor: '
'; case TFile: var path = ide.getPath(v); var url = ide.getUnCachedUrl(path); var ext = v.split(".").pop().toLowerCase(); if (v == "") return '#MISSING'; var html = StringTools.htmlEscape(v); html = '' + html + ''; if (!editor.quickExists(path)) return '$html'; else if( ext == "png" || ext == "jpg" || ext == "jpeg" || ext == "gif" ) { var dims = imageDims.get(url); var dimsText = dims != null ? dims.width+"x"+dims.height : ""; var onload = dims != null ? "" : 'onload="hide.comp.cdb.Cell.onImageLoad(this, \'$url\');"'; var img = ''; var previewHandler = ' onmouseenter="$(this).find(\'.previewContent\').css(\'top\', (this.getBoundingClientRect().bottom - this.offsetHeight) + \'px\')"'; if (getCellConfigValue("inlineImageFiles", false)) { html = '
$html
$dimsText
$img
'; } else { html = '$html
$dimsText
$img
'; } } return html + ' '; case TTilePos: return tileHtml(v); case TTileLayer: var v : cdb.Types.TileLayer = v; var path = ide.getPath(v.file); if( !editor.quickExists(path) ) '' + v.file + ''; else '#DATA'; case TDynamic: var str = Std.string(v).split("\n").join(" ").split("\t").join(""); if( str.length > 50 ) str = str.substr(0, 47) + "..."; str; } } static function onImageLoad(img : js.html.ImageElement, url) { var dims = {width : img.width, height : img.height}; imageDims.set(url, dims); new Element(img).parent().find(".label").text(dims.width+'x'+dims.height); } static var KWDS = ["for","if","var","this","while","else","do","break","continue","switch","function","return","new","throw","try","catch","case","default"]; static var KWD_REG = new EReg([for( k in KWDS ) "(\\b"+k+"\\b)"].join("|"),"g"); function colorizeScript( c : cdb.Data.Column, ecode : String, objID : String ) { var code = ecode; code = StringTools.htmlEscape(code); code = code.split("\n").join("
"); code = code.split("\t").join("    "); // typecheck var error = new ScriptEditor.ScriptChecker(editor.config, "cdb."+getDocumentName()+(c == this.column ? "" : "."+ c.name), [ "cdb."+table.sheet.name => line.obj, "cdb.objID" => objID, "cdb.groupID" => line.getGroupID(), ] ).check(ecode); if( error != null ) return ''+code+''; // strings code = ~/("[^"]*")/g.replace(code,'$1'); code = ~/('[^']*')/g.replace(code,'$1'); // keywords code = KWD_REG.map(code, function(r) return '${r.matched(0)}'); // comments function unspan(str:String) { return str.split(''+unspan(r.matched(1))+''); code = code.split("
").map(function(line) return ~/(\/\/.*)/.map(line,(r) -> ''+unspan(r.matched(1))+'')).join("
"); return code; } public function getGroup() : String { var gid : Null = Reflect.field(line.obj, "group"); if( gid == null ) return null; return table.sheet.props.separatorTitles[gid-1]; } public function getDocumentName() { var name = table.sheet.name.split("@").join("."); if( table.sheet.props.hasGroup ) { var g = getGroup(); if( g != null ) name += "[group="+g+"]"; } name += "."+column.name; return name; } function tileHtml( v : cdb.Types.TilePos, ?isInline ) { var path = ide.getPath(v.file); if( !editor.quickExists(path) ) { if( isInline ) return ""; return '' + v.file + ''; } var width = v.size * (v.width == null?1:v.width); var height = v.size * (v.height == null?1:v.height); var max = width > height ? width : height; var zoom = max <= 32 ? 2 : 64 / max; var inl = isInline ? 'display:inline-block;' : ''; var url = ide.getUnCachedUrl(path); var px = Std.int(v.size*v.x*zoom); var py = Std.int(v.size*v.y*zoom); var bg = 'background : url($url) -${px}px -${py}px;'; if( zoom > 1 ) bg += "image-rendering : pixelated;"; var html = '
'; html += ''; watchFile(path); return html; } static function startTileLoading() { var tiles = new Element(".tile.toload"); if( tiles.length == 0 ) return; tiles.removeClass("toload"); var imap = new Map(); for( t in tiles ) { imap.set(t.getAttribute("path"), t); } for( path => elt in imap ) { var url = Ide.inst.getUnCachedUrl(path); function handleDims(dims: {width : Int, height : Int}) { for( t in tiles ) { if( t.getAttribute("path") == path ) { var zoom = Std.parseFloat(t.getAttribute("zoom")); var bgw = Std.int(dims.width * zoom); var bgh = Std.int(dims.height * zoom); var t1 = new Element(t); t1.css("background-size", '${bgw}px ${bgh}px'); t1.css("opacity", "1"); } } } var dims = imageDims.get(url); if( dims != null ) { handleDims(dims); continue; } var img = js.Browser.document.createImageElement(); img.src = url; img.setAttribute("style","display:none"); img.onload = function() { var dims = {width : img.width, height : img.height}; handleDims(dims); imageDims.set(url, dims); img.remove(); }; elt.parentElement.append(img); } } public function isTextInput() { return switch( column.type ) { case TString if( column.kind == Script ): return false; case TString, TInt, TFloat, TId, TCustom(_), TDynamic, TRef(_): return true; default: return false; } } public function focus() { editor.focus(); editor.cursor.set(table, this.columnIndex, this.line.index); } public function edit() { if( !canEdit() ) return; switch( column.type ) { case TString if( column.kind == Script ): open(); case TInt, TFloat, TString, TId, TCustom(_), TDynamic: var str = value == null ? "" : editor.base.valToString(column.type, value); var textSpan = element.wrapInner("").find("span"); var textHeight = textSpan.height(); var textWidth = textSpan.width(); var longText = textHeight > 25 || str.indexOf("\n") >= 0; element.empty(); element.addClass("edit"); var i = new Element(longText ? "