Cell.hx 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911
  1. package hide.comp.cdb;
  2. import hxd.Key in K;
  3. class Cell extends Component {
  4. static var typeNames = [for( t in Type.getEnumConstructs(cdb.Data.ColumnType) ) t.substr(1).toLowerCase()];
  5. static var imageDims : Map<String, {width : Int, height : Int}> = new Map();
  6. var editor : Editor;
  7. var currentValue : Dynamic;
  8. var watches : Array<String> = new Array<String>();
  9. public var line(default,null) : Line;
  10. public var column(default, null) : cdb.Data.Column;
  11. public var columnIndex(get, never) : Int;
  12. public var value(get, never) : Dynamic;
  13. public var table(get, never) : Table;
  14. public function new( root : Element, line : Line, column : cdb.Data.Column ) {
  15. super(null,root);
  16. this.line = line;
  17. this.editor = line.table.editor;
  18. this.column = column;
  19. @:privateAccess line.cells.push(this);
  20. root.addClass("t_" + typeNames[column.type.getIndex()]);
  21. root.addClass("n_" + column.name);
  22. if(line.table.parent == null) {
  23. var editProps = editor.getColumnProps(column);
  24. root.toggleClass("cat", editProps.categories != null);
  25. if(editProps.categories != null)
  26. for(c in editProps.categories)
  27. root.addClass("cat-" + c);
  28. var visible = editor.isColumnVisible(column);
  29. root.toggleClass("hidden", !visible);
  30. if(!visible)
  31. return;
  32. }
  33. // Used to get the Cell component back from its DOM/Jquery element
  34. root.prop("cellComp", this);
  35. if( column.kind == Script ) root.addClass("t_script");
  36. refresh();
  37. switch( column.type ) {
  38. case TList, TProperties:
  39. element.click(function(e) {
  40. if( e.shiftKey ) return;
  41. e.stopPropagation();
  42. line.table.toggleList(this);
  43. });
  44. case TString if( column.kind == Script ):
  45. element.click(function(_) edit());
  46. default:
  47. if( canEdit() )
  48. element.dblclick(function(_) edit());
  49. else
  50. root.addClass("t_readonly");
  51. }
  52. root.click(function(e) {
  53. editor.cursor.clickCell(this, e.shiftKey);
  54. e.stopPropagation();
  55. });
  56. root.contextmenu(function(e) {
  57. showMenu();
  58. e.stopPropagation();
  59. e.preventDefault();
  60. });
  61. }
  62. public function dragDropFile( relativePath : String, isDrop : Bool = false ) : Bool {
  63. if ( !canEdit() || column.type != TFile) return false;
  64. if ( isDrop ) {
  65. setValue(relativePath);
  66. refresh();
  67. }
  68. return true;
  69. }
  70. function evaluate() {
  71. var f = editor.formulas.get(this);
  72. if( f == null ) return;
  73. var newV : Float = try f.call(line.obj) catch( e : Dynamic ) Math.NaN;
  74. if( newV != currentValue ) {
  75. currentValue = newV;
  76. Reflect.setField(line.obj, column.name, newV);
  77. refresh();
  78. }
  79. }
  80. function showMenu() {
  81. var menu : Array<hide.comp.ContextMenu.ContextMenuItem> = null;
  82. switch( column.type ) {
  83. case TRef(_):
  84. if( value != null && value != "" )
  85. menu = [
  86. {
  87. label : "Goto",
  88. click : () -> @:privateAccess editor.gotoReference(this),
  89. keys : this.editor.config.get("key.cdb.gotoReference"),
  90. },
  91. ];
  92. case TInt, TFloat:
  93. function setF( f : Formulas.Formula ) {
  94. editor.beginChanges();
  95. editor.formulas.set(this, f);
  96. line.evaluate();
  97. editor.endChanges();
  98. refresh();
  99. }
  100. var forms : Array<hide.comp.ContextMenu.ContextMenuItem>;
  101. var current = editor.formulas.get(this);
  102. forms = [for( f in editor.formulas.getList(table.sheet) ) { label : f.name, click : () -> if( f == current ) setF(null) else setF(f), checked : f == current }];
  103. forms.push({ label : "New...", click : () -> editor.formulas.createNew(this, setF) });
  104. menu = [
  105. { label : "Formula", menu : forms }
  106. ];
  107. default:
  108. }
  109. if( menu != null ) {
  110. focus();
  111. new ContextMenu(menu);
  112. }
  113. }
  114. public function canEdit() {
  115. return table.canEditColumn(column.name);
  116. }
  117. function get_table() return line.table;
  118. function get_columnIndex() return table.columns.indexOf(column);
  119. inline function get_value() return currentValue;
  120. function getCellConfigValue<T>( name : String, ?def : T ) : T
  121. {
  122. var cfg = ide.currentConfig;
  123. var paths = table.sheet.name.split("@");
  124. paths.unshift("cdb");
  125. paths.push(column.name);
  126. while ( paths.length != 0 ) {
  127. var config = cfg.get(paths.join("."), null);
  128. if ( config != null && Reflect.hasField(config, name) )
  129. return Reflect.field(config, name);
  130. paths.pop();
  131. }
  132. return def;
  133. }
  134. static var R_HTML = ~/[<&]/;
  135. public function refresh(withSubtable = false) {
  136. currentValue = Reflect.field(line.obj, column.name);
  137. var html = valueHtml(column, value, line.table.getRealSheet(), line.obj, []);
  138. if( html == "&nbsp;" ) element.text(" ") else if( !R_HTML.match(html) ) element.text(html) else element.html(html);
  139. updateClasses();
  140. var subTable = line.subTable;
  141. if( withSubtable && subTable != null && subTable.cell == this) {
  142. if( column.type == TString && column.kind == Script )
  143. subTable.refresh();
  144. else
  145. table.refreshList(this);
  146. }
  147. }
  148. function watchFile( file : String ) {
  149. if( file == null ) return;
  150. if( watches.indexOf(file) != -1 ) return;
  151. watches.push(file);
  152. ide.fileWatcher.register(file, function() { refresh(); }, true, element);
  153. }
  154. function updateClasses() {
  155. element.removeClass("edit");
  156. element.removeClass("edit_long");
  157. switch( column.type ) {
  158. case TBool:
  159. element.toggleClass("true", value == true);
  160. element.toggleClass("false", value == false);
  161. case TInt, TFloat:
  162. element.toggleClass("zero", value == 0 );
  163. element.toggleClass("nan", Math.isNaN(value));
  164. element.toggleClass("formula", editor.formulas.has(this) );
  165. default:
  166. }
  167. }
  168. function getSheetView( sheet : cdb.Sheet ) {
  169. var view = table.editor.view;
  170. if( view == null )
  171. return null;
  172. var path = sheet.name.split("@");
  173. var view = view.get(path.shift());
  174. for( name in path ) {
  175. var sub = view.sub == null ? null : view.sub.get(name);
  176. if( sub == null )
  177. return null;
  178. view = sub;
  179. }
  180. return view;
  181. }
  182. function canViewSubColumn( sheet : cdb.Sheet, column : String ) {
  183. var view = getSheetView(sheet);
  184. return view == null || view.show == null || view.show.indexOf(column) >= 0;
  185. }
  186. var _cachedScope : Array<{ s : cdb.Sheet, obj : Dynamic }>;
  187. function getScope() {
  188. if( _cachedScope != null ) return _cachedScope;
  189. var scope = [];
  190. var line = line;
  191. while( true ) {
  192. var p = Std.downcast(line.table, SubTable);
  193. if( p == null ) break;
  194. line = p.cell.line;
  195. scope.unshift({ s : line.table.getRealSheet(), obj : line.obj });
  196. }
  197. return _cachedScope = scope;
  198. }
  199. function makeId( scopes : Array<{ s : cdb.Sheet, obj : Dynamic }>, scope : Int, id : String ) {
  200. var ids = [];
  201. if( id != null ) ids.push(id);
  202. var pos = scopes.length;
  203. while( true ) {
  204. pos -= scope;
  205. if( pos < 0 ) {
  206. scopes = getScope();
  207. pos += scopes.length;
  208. }
  209. var s = scopes[pos];
  210. var pid = Reflect.field(s.obj, s.s.idCol.name);
  211. if( pid == null ) return "";
  212. ids.unshift(pid);
  213. scope = s.s.idCol.scope;
  214. if( scope == null ) break;
  215. }
  216. return ids.join(":");
  217. }
  218. function refScope( targetSheet : cdb.Sheet, currentSheet : cdb.Sheet, obj : Dynamic, localScope : Array<{ s : cdb.Sheet, obj : Dynamic }> ) {
  219. var targetDepth = targetSheet.name.split("@").length;
  220. var scope = getScope().concat(localScope);
  221. if( scope.length < targetDepth )
  222. scope.push({ s : currentSheet, obj : obj });
  223. while( scope.length >= targetDepth )
  224. scope.pop();
  225. return scope;
  226. }
  227. function valueHtml( c : cdb.Data.Column, v : Dynamic, sheet : cdb.Sheet, obj : Dynamic, scope : Array<{ s : cdb.Sheet, obj : Dynamic }> ) : String {
  228. if( v == null ) {
  229. if( c.opt )
  230. return "&nbsp;";
  231. return '<span class="error">#NULL</span>';
  232. }
  233. return switch( c.type ) {
  234. case TInt, TFloat:
  235. switch( c.display ) {
  236. case Percent:
  237. (Math.round(v * 10000)/100) + "%";
  238. default:
  239. v + "";
  240. }
  241. case TId:
  242. if( v == "" )
  243. '<span class="error">#MISSING</span>';
  244. else {
  245. var id = c.scope != null ? makeId(scope,c.scope,v) : v;
  246. editor.isUniqueID(sheet,obj,id) ? v : '<span class="error">#DUP($v)</span>';
  247. }
  248. case TString if( c.kind == Script ): // wrap content in div because td cannot have max-height
  249. v == "" ? "&nbsp;" : '<div class="script">${colorizeScript(c,v, sheet.idCol == null ? null : Reflect.field(obj, sheet.idCol.name))}</div>';
  250. case TString, TLayer(_):
  251. v == "" ? "&nbsp;" : StringTools.htmlEscape(v).split("\n").join("<br/>");
  252. case TRef(sname):
  253. if( v == "" )
  254. '<span class="error">#MISSING</span>';
  255. else {
  256. var s = editor.base.getSheet(sname);
  257. var i = s.index.get(s.idCol.scope != null ? makeId(refScope(s,sheet,obj,scope),s.idCol.scope,v) : v);
  258. i == null ? '<span class="error">#REF($v)</span>' : (i.ico == null ? "" : tileHtml(i.ico,true)+" ") + StringTools.htmlEscape(i.disp);
  259. }
  260. case TBool:
  261. v?"Y":"N";
  262. case TEnum(values):
  263. values[v];
  264. case TImage:
  265. '<span class="error">#DEPRECATED</span>';
  266. case TList:
  267. var a : Array<Dynamic> = v;
  268. var ps = sheet.getSub(c);
  269. var out : Array<String> = [];
  270. var size = 0;
  271. scope.push({ s : sheet, obj : obj });
  272. for( v in a ) {
  273. var vals = [];
  274. for( c in ps.columns ) {
  275. if( !canViewSubColumn(ps, c.name) ) continue;
  276. var h = valueHtml(c, Reflect.field(v, c.name), ps, v, scope);
  277. if( h != "" && h != "&nbsp;" )
  278. vals.push(h);
  279. }
  280. inline function char(s) return '<span class="minor">$s</span>';
  281. var v = vals.length == 1 ? vals[0] : (char('[') + vals.join(char(',')) + char(']'));
  282. if( size > 200 ) {
  283. out.push("...");
  284. break;
  285. }
  286. var vstr = v;
  287. if( v.indexOf("<") >= 0 ) {
  288. vstr = ~/<img src="[^"]+" style="display:none"[^>]+>/g.replace(vstr, "");
  289. vstr = ~/<img src="[^"]+"\/>/g.replace(vstr, "[I]");
  290. vstr = ~/<div id="[^>]+><\/div>/g.replace(vstr, "[D]");
  291. }
  292. vstr = StringTools.trim(vstr);
  293. size += vstr.length;
  294. out.push(v);
  295. }
  296. scope.pop();
  297. if( out.length == 0 )
  298. return "";
  299. return out.join(", ");
  300. case TProperties:
  301. var ps = sheet.getSub(c);
  302. var out = [];
  303. scope.push({ s : sheet, obj : obj });
  304. for( c in ps.columns ) {
  305. var pval = Reflect.field(v, c.name);
  306. if( pval == null && c.opt ) continue;
  307. if( !canViewSubColumn(ps, c.name) ) continue;
  308. out.push(c.name+" : "+valueHtml(c, pval, ps, v, scope));
  309. }
  310. scope.pop();
  311. return out.join("<br/>");
  312. case TCustom(name):
  313. var t = editor.base.getCustomType(name);
  314. var a : Array<Dynamic> = v;
  315. var cas = t.cases[a[0]];
  316. var str = cas.name;
  317. if( cas.args.length > 0 ) {
  318. str += "(";
  319. var out = [];
  320. var pos = 1;
  321. for( i in 1...a.length )
  322. out.push(valueHtml(cas.args[i-1], a[i], sheet, this, scope));
  323. str += out.join(",");
  324. str += ")";
  325. }
  326. str;
  327. case TFlags(values):
  328. var v : Int = v;
  329. var view = getSheetView(sheet);
  330. if( view != null && view.options != null ) {
  331. var mask = Reflect.field(view.options,c.name);
  332. if( mask != null ) v &= mask;
  333. }
  334. var flags = [];
  335. for( i in 0...values.length )
  336. if( v & (1 << i) != 0 )
  337. flags.push(StringTools.htmlEscape(values[i]));
  338. flags.length == 0 ? String.fromCharCode(0x2205) : flags.join("|"+String.fromCharCode(0x200B));
  339. case TColor:
  340. '<div class="color" style="background-color:#${StringTools.hex(v,6)}"></div>';
  341. case TFile:
  342. var path = ide.getPath(v);
  343. var url = ide.getUnCachedUrl(path);
  344. var ext = v.split(".").pop().toLowerCase();
  345. if (v == "") return '<span class="error">#MISSING</span>';
  346. var html = StringTools.htmlEscape(v);
  347. html = '<span title=\'$html\' >' + html + '</span>';
  348. if (!editor.quickExists(path)) return '<span class="error">$html</span>';
  349. else if( ext == "png" || ext == "jpg" || ext == "jpeg" || ext == "gif" ) {
  350. var dims = imageDims.get(url);
  351. var dimsText = dims != null ? dims.width+"x"+dims.height : "";
  352. var onload = dims != null ? "" : 'onload="hide.comp.cdb.Cell.onImageLoad(this, \'$url\');"';
  353. var img = '<img src="$url" $onload/>';
  354. var previewHandler = ' onmouseenter="$(this).find(\'.previewContent\').css(\'top\', (this.getBoundingClientRect().bottom - this.offsetHeight) + \'px\')"';
  355. if (getCellConfigValue("inlineImageFiles", false)) {
  356. html = '<span class="preview inlineImage" $previewHandler>
  357. <img src="$url"><div class="previewContent"><div class="inlineImagePath">$html</div><div class="label">$dimsText</div>$img</div>
  358. </span>';
  359. } else {
  360. html = '<span class="preview" $previewHandler>$html
  361. <div class="previewContent"><div class="label">$dimsText</div>$img</div>
  362. </span>';
  363. }
  364. }
  365. return html + ' <input type="submit" value="open" onclick="hide.Ide.inst.openFile(\'$path\')"/>';
  366. case TTilePos:
  367. return tileHtml(v);
  368. case TTileLayer:
  369. var v : cdb.Types.TileLayer = v;
  370. var path = ide.getPath(v.file);
  371. if( !editor.quickExists(path) )
  372. '<span class="error">' + v.file + '</span>';
  373. else
  374. '#DATA';
  375. case TDynamic:
  376. var str = Std.string(v).split("\n").join(" ").split("\t").join("");
  377. if( str.length > 50 ) str = str.substr(0, 47) + "...";
  378. str;
  379. }
  380. }
  381. static function onImageLoad(img : js.html.ImageElement, url) {
  382. var dims = {width : img.width, height : img.height};
  383. imageDims.set(url, dims);
  384. new Element(img).parent().find(".label").text(dims.width+'x'+dims.height);
  385. }
  386. static var KWDS = ["for","if","var","this","while","else","do","break","continue","switch","function","return","new","throw","try","catch","case","default"];
  387. static var KWD_REG = new EReg([for( k in KWDS ) "(\\b"+k+"\\b)"].join("|"),"g");
  388. function colorizeScript( c : cdb.Data.Column, ecode : String, objID : String ) {
  389. var code = ecode;
  390. code = StringTools.htmlEscape(code);
  391. code = code.split("\n").join("<br/>");
  392. code = code.split("\t").join("&nbsp;&nbsp;&nbsp;&nbsp;");
  393. // typecheck
  394. var error = new ScriptEditor.ScriptChecker(editor.config, "cdb."+getDocumentName()+(c == this.column ? "" : "."+ c.name),
  395. [
  396. "cdb."+table.sheet.name => line.obj,
  397. "cdb.objID" => objID,
  398. "cdb.groupID" => line.getGroupID(),
  399. ]
  400. ).check(ecode);
  401. if( error != null )
  402. return '<span class="error">'+code+'</span>';
  403. // strings
  404. code = ~/("[^"]*")/g.replace(code,'<span class="str">$1</span>');
  405. code = ~/('[^']*')/g.replace(code,'<span class="str">$1</span>');
  406. // keywords
  407. code = KWD_REG.map(code, function(r) return '<span class="kwd">${r.matched(0)}</span>');
  408. // comments
  409. function unspan(str:String) {
  410. return str.split('<span class="').join('<span class="_');
  411. }
  412. code = ~/(\/\*([^\*]+)\*\/)/g.map(code,function(r) return '<span class="comment">'+unspan(r.matched(1))+'</span>');
  413. code = code.split("<br/>").map(function(line) return ~/(\/\/.*)/.map(line,(r) -> '<span class="comment">'+unspan(r.matched(1))+'</span>')).join("<br/>");
  414. return code;
  415. }
  416. public function getGroup() : String {
  417. var gid : Null<Int> = Reflect.field(line.obj, "group");
  418. if( gid == null ) return null;
  419. return table.sheet.props.separatorTitles[gid-1];
  420. }
  421. public function getDocumentName() {
  422. var name = table.sheet.name.split("@").join(".");
  423. if( table.sheet.props.hasGroup ) {
  424. var g = getGroup();
  425. if( g != null ) name += "[group="+g+"]";
  426. }
  427. name += "."+column.name;
  428. return name;
  429. }
  430. function tileHtml( v : cdb.Types.TilePos, ?isInline ) {
  431. var path = ide.getPath(v.file);
  432. if( !editor.quickExists(path) ) {
  433. if( isInline ) return "";
  434. return '<span class="error">' + v.file + '</span>';
  435. }
  436. var width = v.size * (v.width == null?1:v.width);
  437. var height = v.size * (v.height == null?1:v.height);
  438. var max = width > height ? width : height;
  439. var zoom = max <= 32 ? 2 : 64 / max;
  440. var inl = isInline ? 'display:inline-block;' : '';
  441. var url = ide.getUnCachedUrl(path);
  442. var px = Std.int(v.size*v.x*zoom);
  443. var py = Std.int(v.size*v.y*zoom);
  444. var bg = 'background : url($url) -${px}px -${py}px;';
  445. if( zoom > 1 )
  446. bg += "image-rendering : pixelated;";
  447. var html = '<div
  448. class="tile toload"
  449. path="$path"
  450. zoom="$zoom"
  451. style="width : ${Std.int(width * zoom)}px; height : ${Std.int(height * zoom)}px; opacity:0; $bg $inl"
  452. ></div>';
  453. html += '<script>hide.comp.cdb.Cell.startTileLoading()</script>';
  454. watchFile(path);
  455. return html;
  456. }
  457. static function startTileLoading() {
  458. var tiles = new Element(".tile.toload");
  459. if( tiles.length == 0 ) return;
  460. tiles.removeClass("toload");
  461. var imap = new Map();
  462. for( t in tiles ) {
  463. imap.set(t.getAttribute("path"), t);
  464. }
  465. for( path => elt in imap ) {
  466. var url = Ide.inst.getUnCachedUrl(path);
  467. function handleDims(dims: {width : Int, height : Int}) {
  468. for( t in tiles ) {
  469. if( t.getAttribute("path") == path ) {
  470. var zoom = Std.parseFloat(t.getAttribute("zoom"));
  471. var bgw = Std.int(dims.width * zoom);
  472. var bgh = Std.int(dims.height * zoom);
  473. var t1 = new Element(t);
  474. t1.css("background-size", '${bgw}px ${bgh}px');
  475. t1.css("opacity", "1");
  476. }
  477. }
  478. }
  479. var dims = imageDims.get(url);
  480. if( dims != null ) {
  481. handleDims(dims);
  482. continue;
  483. }
  484. var img = js.Browser.document.createImageElement();
  485. img.src = url;
  486. img.setAttribute("style","display:none");
  487. img.onload = function() {
  488. var dims = {width : img.width, height : img.height};
  489. handleDims(dims);
  490. imageDims.set(url, dims);
  491. img.remove();
  492. };
  493. elt.parentElement.append(img);
  494. }
  495. }
  496. public function isTextInput() {
  497. return switch( column.type ) {
  498. case TString if( column.kind == Script ):
  499. return false;
  500. case TString, TInt, TFloat, TId, TCustom(_), TDynamic, TRef(_):
  501. return true;
  502. default:
  503. return false;
  504. }
  505. }
  506. public function focus() {
  507. editor.focus();
  508. editor.cursor.set(table, this.columnIndex, this.line.index);
  509. }
  510. public function edit() {
  511. if( !canEdit() )
  512. return;
  513. switch( column.type ) {
  514. case TString if( column.kind == Script ):
  515. open();
  516. case TInt, TFloat, TString, TId, TCustom(_), TDynamic:
  517. var str = value == null ? "" : editor.base.valToString(column.type, value);
  518. var textSpan = element.wrapInner("<span>").find("span");
  519. var textHeight = textSpan.height();
  520. var textWidth = textSpan.width();
  521. var longText = textHeight > 25 || str.indexOf("\n") >= 0;
  522. element.empty();
  523. element.addClass("edit");
  524. var i = new Element(longText ? "<textarea>" : "<input>").appendTo(element);
  525. i.keypress(function(e) e.stopPropagation());
  526. i.dblclick(function(e) e.stopPropagation());
  527. //if( str != "" && (table.displayMode == Properties || table.displayMode == AllProperties) )
  528. // i.css({ width : Math.ceil(textWidth - 3) + "px" }); -- bug if small text ?
  529. if( longText ) {
  530. element.addClass("edit_long");
  531. i.css({ height : Math.max(25,Math.ceil(textHeight - 1)) + "px" });
  532. }
  533. i.val(str);
  534. i.keydown(function(e) {
  535. switch( e.keyCode ) {
  536. case K.ESCAPE:
  537. refresh();
  538. table.editor.element.focus();
  539. case K.ENTER if( !e.shiftKey || !column.type.match(TString|TDynamic|TCustom(_)) ):
  540. closeEdit();
  541. e.preventDefault();
  542. case K.ENTER if( !longText ):
  543. var old = currentValue;
  544. // hack to insert newline and tranform the input into textarea
  545. var newVal = i.val() + "\n";
  546. Reflect.setField(line.obj, column.name, newVal+"x");
  547. refresh();
  548. Reflect.setField(line.obj, column.name,old);
  549. currentValue = newVal;
  550. edit();
  551. (cast element.find("textarea")[0] : js.html.TextAreaElement).setSelectionRange(newVal.length,newVal.length);
  552. e.preventDefault();
  553. case K.UP, K.DOWN if( !longText ):
  554. closeEdit();
  555. return;
  556. case K.TAB:
  557. closeEdit();
  558. e.preventDefault();
  559. editor.cursor.move(e.shiftKey ? -1 : 1, 0, false, false);
  560. var c = editor.cursor.getCell();
  561. if( c != this ) c.edit();
  562. }
  563. e.stopPropagation();
  564. });
  565. i.keyup(function(_) try {
  566. editor.base.parseValue(column.type, i.val());
  567. setErrorMessage(null);
  568. } catch( e : Dynamic ) {
  569. setErrorMessage(StringTools.htmlUnescape(""+e));
  570. });
  571. i.keyup(null);
  572. i.blur(function(_) closeEdit());
  573. i.focus();
  574. i.select();
  575. if( longText ) i.scrollTop(0);
  576. case TBool:
  577. setValue( currentValue == false && column.opt && table.displayMode != Properties ? null : currentValue == null ? true : currentValue ? false : true );
  578. refresh();
  579. case TProperties, TList:
  580. open();
  581. case TRef(name):
  582. var sdat = editor.base.getSheet(name);
  583. if( sdat == null ) return;
  584. element.empty();
  585. element.addClass("edit");
  586. var s = new Element("<select>");
  587. var isLocal = sdat.idCol.scope != null;
  588. var elts;
  589. if( isLocal ) {
  590. var scope = refScope(sdat,table.getRealSheet(),line.obj,[]);
  591. var prefix = makeId(scope, sdat.idCol.scope, null)+":";
  592. elts = [for( d in sdat.all ) if( StringTools.startsWith(d.id,prefix) ) { id : d.id.split(":").pop(), ico : d.ico, text : d.disp }];
  593. } else
  594. elts = [for( d in sdat.all ) { id : d.id, ico : d.ico, text : d.disp }];
  595. if( column.opt || currentValue == null || currentValue == "" )
  596. elts.unshift( { id : "~", ico : null, text : "--- None ---" } );
  597. element.append(s);
  598. var props : Dynamic = { data : elts };
  599. if( sdat.props.displayIcon != null ) {
  600. function buildElement(i) {
  601. var text = StringTools.htmlEscape(i.text);
  602. return new Element("<div>"+(i.ico == null ? "<div style='display:inline-block;width:16px'/>" : tileHtml(i.ico,true)) + " " + text+"</div>");
  603. }
  604. props.templateResult = props.templateSelection = buildElement;
  605. }
  606. (untyped s.select2)(props);
  607. (untyped s.select2)("val", currentValue == null ? "" : currentValue);
  608. (untyped s.select2)("open");
  609. var sel2 = s.data("select2");
  610. sel2.$dropdown.find("input").on("keydown", function(e) {
  611. e.stopPropagation();
  612. });
  613. s.change(function(e) {
  614. var val = s.val();
  615. if( val == "~" ) val = null;
  616. setValue(val);
  617. sel2.close();
  618. closeEdit();
  619. });
  620. new Element("input.select2-search__field").keydown(function(e) e.stopPropagation());
  621. s.on("select2:close", function(_) closeEdit());
  622. case TEnum(values):
  623. element.empty();
  624. element.addClass("edit");
  625. var s = new Element("<select>");
  626. var elts = [for( i in 0...values.length ){ id : ""+i, ico : null, text : values[i] }];
  627. if( column.opt )
  628. elts.unshift( { id : "-1", ico : null, text : "--- None ---" } );
  629. element.append(s);
  630. var props : Dynamic = { data : elts };
  631. (untyped s.select2)(props);
  632. (untyped s.select2)("val", currentValue == null ? "" : currentValue);
  633. (untyped s.select2)("open");
  634. var sel2 = s.data("select2");
  635. s.change(function(e) {
  636. var val = Std.parseInt(s.val());
  637. if( val < 0 ) val = null;
  638. setValue(val);
  639. sel2.close();
  640. closeEdit();
  641. });
  642. new Element("input.select2-search__field").keydown(function(e) {
  643. switch( e.keyCode ) {
  644. case K.LEFT, K.RIGHT:
  645. s.blur();
  646. return;
  647. case K.TAB:
  648. s.blur();
  649. editor.cursor.move(e.shiftKey? -1:1, 0, false, false);
  650. var c = editor.cursor.getCell();
  651. if( c != this ) c.edit();
  652. e.preventDefault();
  653. default:
  654. }
  655. e.stopPropagation();
  656. });
  657. s.on("select2:close", function(_) closeEdit());
  658. case TColor:
  659. var modal = new Element("<div>").addClass("hide-modal").appendTo(element);
  660. var color = new ColorPicker(element);
  661. color.value = currentValue;
  662. color.open();
  663. color.onChange = function(drag) {
  664. element.find(".color").css({backgroundColor : '#'+StringTools.hex(color.value,6) });
  665. };
  666. color.onClose = function() {
  667. setValue(color.value);
  668. color.remove();
  669. closeEdit();
  670. };
  671. modal.click(function(_) color.close());
  672. case TFile:
  673. ide.chooseFile(["*"], function(file) {
  674. setValue(file);
  675. refresh();
  676. }, false, currentValue);
  677. case TFlags(values):
  678. var div = new Element("<div>").addClass("flagValues");
  679. div.click(function(e) e.stopPropagation()).dblclick(function(e) e.stopPropagation());
  680. var view = table.view;
  681. var mask = -1;
  682. if( view != null && view.options != null ) {
  683. var m = Reflect.field(view.options,column.name);
  684. if( m != null ) mask = m;
  685. }
  686. var val = currentValue;
  687. for( i in 0...values.length ) {
  688. if( mask & (1<<i) == 0 ) continue;
  689. var f = new Element("<input>").attr("type", "checkbox").prop("checked", val & (1 << i) != 0).change(function(e) {
  690. val &= ~(1 << i);
  691. if( e.getThis().prop("checked") ) val |= 1 << i;
  692. e.stopPropagation();
  693. });
  694. new Element("<label>").text(values[i]).appendTo(div).prepend(f);
  695. }
  696. element.empty();
  697. var modal = new Element("<div>").addClass("hide-modal").appendTo(element);
  698. element.append(div);
  699. modal.click(function(e) {
  700. setValue(val);
  701. refresh();
  702. });
  703. case TTilePos:
  704. var modal = new hide.comp.Modal(element);
  705. modal.modalClick = function(_) closeEdit();
  706. var t : cdb.Types.TilePos = currentValue;
  707. var file = t == null ? null : t.file;
  708. var size = t == null ? 16 : t.size;
  709. var pos = t == null ? { x : 0, y : 0, width : 1, height : 1 } : { x : t.x, y : t.y, width : t.width == null ? 1 : t.width, height : t.height == null ? 1 : t.height };
  710. if( file == null ) {
  711. var y = line.index - 1;
  712. while( y >= 0 ) {
  713. var o = line.table.lines[y--];
  714. var v2 = Reflect.field(o.obj, column.name);
  715. if( v2 != null ) {
  716. file = v2.file;
  717. size = v2.size;
  718. break;
  719. }
  720. }
  721. }
  722. function setVal() {
  723. var v : Dynamic = { file : file, size : size, x : pos.x, y : pos.y };
  724. if( pos.width != 1 ) v.width = pos.width;
  725. if( pos.height != 1 ) v.height = pos.height;
  726. setRawValue(v);
  727. }
  728. if( file == null ) {
  729. ide.chooseImage(function(path) {
  730. if( path == null ) {
  731. closeEdit();
  732. return;
  733. }
  734. file = path;
  735. setVal();
  736. closeEdit();
  737. edit();
  738. },true);
  739. return;
  740. }
  741. var ts = new hide.comp.TileSelector(file,size,modal.content);
  742. ts.allowRectSelect = true;
  743. ts.allowSizeSelect = true;
  744. ts.allowFileChange = true;
  745. ts.value = pos;
  746. ts.onChange = function(cancel) {
  747. if( !cancel ) {
  748. file = ts.file;
  749. size = ts.size;
  750. pos = ts.value;
  751. setVal();
  752. }
  753. refresh();
  754. focus();
  755. };
  756. case TLayer(_), TTileLayer:
  757. // no edit
  758. case TImage:
  759. // deprecated
  760. }
  761. }
  762. public function open( ?immediate : Bool ) {
  763. if( column.type == TString && column.kind == Script ) {
  764. // prevent opening the script if we are undo/redo-ing as this
  765. // will get our script windowed focus and prevent further undo/redo action
  766. if( immediate && !Editor.inRefreshAll ) return;
  767. var str = value == null ? "" : editor.base.valToString(column.type, value);
  768. table.toggleList(this, immediate, function() return new ScriptTable(editor, this));
  769. } else
  770. table.toggleList(this, immediate);
  771. }
  772. public function setErrorMessage( msg : String ) {
  773. element.find("div.error").remove();
  774. if( msg == null ) return;
  775. new Element("<div>").addClass("error").html(msg).appendTo(element);
  776. }
  777. function setRawValue( str : Dynamic ) {
  778. var newValue : Dynamic;
  779. if( Std.is(str,String) ) {
  780. newValue = try editor.base.parseValue(column.type, str, false) catch( e : Dynamic ) return;
  781. } else
  782. newValue = str;
  783. if( newValue == null || newValue == currentValue )
  784. return;
  785. switch( column.type ) {
  786. case TId:
  787. var obj = line.obj;
  788. var prevValue = value;
  789. var realSheet = table.getRealSheet();
  790. var isLocal = realSheet.idCol.scope != null;
  791. var parentID = isLocal ? makeId([],realSheet.idCol.scope,null) : null;
  792. // most likely our obj, unless there was a #DUP
  793. var prevObj = value != null ? realSheet.index.get(isLocal ? parentID+":"+value : value) : null;
  794. // have we already an obj mapped to the same id ?
  795. var prevTarget = realSheet.index.get(isLocal ? parentID+":"+newValue : newValue);
  796. editor.beginChanges();
  797. if( prevObj == null || prevObj.obj == obj ) {
  798. // remap
  799. var m = new Map();
  800. m.set(value, newValue);
  801. if( isLocal ) {
  802. var scope = getScope();
  803. var parent = scope[scope.length - realSheet.idCol.scope];
  804. editor.base.updateLocalRefs(realSheet, m, parent.obj, parent.s);
  805. } else
  806. editor.base.updateRefs(realSheet, m);
  807. }
  808. setValue(newValue);
  809. editor.endChanges();
  810. editor.refreshRefs();
  811. focus();
  812. /*
  813. // creates or remove a #DUP : need to refresh the whole table
  814. if( prevTarget != null || (prevObj != null && (prevObj.obj != obj || table.sheet.index.get(prevValue) != null)) )
  815. table.refresh();
  816. */
  817. case TString if( column.kind == Script ):
  818. setValue(StringTools.trim(newValue));
  819. case TTilePos:
  820. // if we change a file that has moved, change it for all instances having the same file
  821. editor.beginChanges();
  822. var obj = line.obj;
  823. var change = false;
  824. var oldV : cdb.Types.TilePos = currentValue;
  825. var newV : cdb.Types.TilePos = newValue;
  826. if( newV != null && oldV != null && oldV.file != newV.file && !sys.FileSystem.exists(ide.getPath(oldV.file)) && sys.FileSystem.exists(ide.getPath(newV.file)) ) {
  827. for( l in table.lines) {
  828. if( l == line ) continue;
  829. var t : Dynamic = Reflect.field(l.obj, column.name);
  830. if( t != null && t.file == oldV.file ) {
  831. t.file = newV.file;
  832. change = true;
  833. }
  834. }
  835. }
  836. setValue(newValue);
  837. editor.endChanges();
  838. if( change )
  839. editor.refresh();
  840. default:
  841. setValue(newValue);
  842. }
  843. }
  844. public function setValue( value : Dynamic ) {
  845. currentValue = value;
  846. editor.changeObject(line,column,value);
  847. }
  848. public function closeEdit() {
  849. var str = element.find("input,textarea").val();
  850. if( str != null ) setRawValue(str);
  851. refresh();
  852. focus();
  853. }
  854. }