gcsx_scriptedit.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. /* GCSx
  2. ** SCRIPTEDIT.CPP
  3. **
  4. ** Script support
  5. ** Expands basic Script to include editor functionality
  6. */
  7. /*****************************************************************************
  8. ** Copyright (C) 2003-2006 Janson
  9. **
  10. ** This program is free software; you can redistribute it and/or modify
  11. ** it under the terms of the GNU General Public License as published by
  12. ** the Free Software Foundation; either version 2 of the License, or
  13. ** (at your option) any later version.
  14. **
  15. ** This program is distributed in the hope that it will be useful,
  16. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. ** GNU General Public License for more details.
  19. **
  20. ** You should have received a copy of the GNU General Public License
  21. ** along with this program; if not, write to the Free Software
  22. ** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
  23. *****************************************************************************/
  24. #include "all.h"
  25. ScriptEdit::ScriptEdit(WorldEdit* myWorld, ScriptType type, int myId) : Script(myWorld, type, myId) { start_func
  26. if (myId) assert(myWorld);
  27. // Find an unused "Untitled" name
  28. if (myId) {
  29. int pos = 1;
  30. string findName;
  31. if (type == SCRIPT_NOTE) {
  32. findName = "notes";
  33. while (myWorld->findScriptNote(findName)) {
  34. findName = "notes ";
  35. findName += intToStr(++pos);
  36. }
  37. }
  38. else if (type == SCRIPT_CODE) {
  39. findName = "script";
  40. while (myWorld->findScriptCode(findName)) {
  41. findName = "script ";
  42. findName += intToStr(++pos);
  43. }
  44. }
  45. else {
  46. assert(type == SCRIPT_LIBRARY);
  47. findName = "library";
  48. while (myWorld->findScriptLib(findName)) {
  49. findName = "library ";
  50. findName += intToStr(++pos);
  51. }
  52. }
  53. findName[0] = toupper(findName[0]);
  54. name = findName;
  55. headerModified = contentModified = 1;
  56. }
  57. else {
  58. assert((type == SCRIPT_NOTE) || (type == SCRIPT_CODE) || (type == SCRIPT_LIBRARY));
  59. headerModified = contentModified = 0;
  60. }
  61. nameL = name;
  62. toLower(nameL);
  63. browserNode = NULL;
  64. dependencyModified = sourceModified = 0;
  65. inMiddleOfUndoBlock = 0;
  66. disassociated = 0;
  67. desktop->broadcastObjChange(OBJ_SCRIPT | OBJMOD_CREATE, this, 0, 0);
  68. }
  69. ScriptEdit::~ScriptEdit() { start_func
  70. // @TODO: doesn't appear to be needed; no way to delete accidentally
  71. // if (!disassociated) desktop->broadcastObjChange(OBJ_SCRIPT | OBJMOD_DELETE, this, 0, 0);
  72. // World should be the one deleting us, which will delete our node also
  73. }
  74. void ScriptEdit::setInfo(WorldEdit* myWorld, int newId) { start_func
  75. world = myWorld;
  76. id = newId;
  77. }
  78. void ScriptEdit::compile() { start_func
  79. assert(lockCount);
  80. cleanForCompile();
  81. Script::compile();
  82. if (!compileErrors) {
  83. // @TODO: mark all dependants as dependencyModified
  84. dependencyModified = 0;
  85. sourceModified = 0;
  86. }
  87. setHeaderModified();
  88. setContentModified();
  89. }
  90. void ScriptEdit::parseFuncs() { start_func
  91. assert(lockCount);
  92. cleanForCompile();
  93. Script::parseFuncs();
  94. setHeaderModified();
  95. setContentModified();
  96. }
  97. void ScriptEdit::clean(int full) { start_func
  98. assert(!cached);
  99. // Full = everything
  100. // otherwise- leave function parse results
  101. if ((full) || (memStatus == CODE_UNCOMPILED)) {
  102. debugWrite(DEBUG_COMPILE, "Cleaning '%s' for recompile... (full)", name.c_str());
  103. if (functions) {
  104. destroyFunctionMap(functions);
  105. delete functions;
  106. functions = NULL;
  107. }
  108. if ((memStatus != CODE_UNCOMPILED) || (diskStatus != CODE_UNCOMPILED)) {
  109. setHeaderModified();
  110. setContentModified();
  111. }
  112. memStatus = CODE_UNCOMPILED;
  113. diskStatus = CODE_UNCOMPILED;
  114. }
  115. else {
  116. debugWrite(DEBUG_COMPILE, "Cleaning '%s' for recompile... (partial)", name.c_str());
  117. if ((memStatus != CODE_PARSED) || (diskStatus != CODE_PARSED)) {
  118. setHeaderModified();
  119. setContentModified();
  120. }
  121. memStatus = CODE_PARSED;
  122. if (diskStatus > CODE_PARSED) diskStatus = CODE_PARSED;
  123. }
  124. delete links;
  125. links = NULL;
  126. delete[] bytecode;
  127. bytecode = NULL;
  128. compileErrors = 0;
  129. compileWarnings = 0;
  130. }
  131. void ScriptEdit::cleanForCompile(int force) { start_func
  132. // @TODO: check all dependencies first, here
  133. // Cleans whatever needs to be cleaned based on modified/etc
  134. // in preparation for a compile
  135. if ((sourceModified) || (compileErrors) || (force) || (memStatus == CODE_UNCOMPILED)) {
  136. clean(1);
  137. }
  138. else if ((dependencyModified) || (memStatus == CODE_PARSED)) {
  139. clean(0);
  140. }
  141. // (always set up for relink or not linked when saving)
  142. else if (memStatus == CODE_LINKED) {
  143. // Don't need to touch diskStatus as it should NEVER be CODE_LINKED
  144. assert(diskStatus != CODE_LINKED);
  145. memStatus = CODE_COMPILED;
  146. }
  147. }
  148. void ScriptEdit::link() { start_func
  149. doLink();
  150. }
  151. void ScriptEdit::decompile() { start_func
  152. assert(lockCount);
  153. cleanForCompile();
  154. compile();
  155. if (bytecode) decompileBytecode(bytecode, links);
  156. }
  157. void ScriptEdit::disassociate() throw_File { start_func
  158. // Ensure not cached- our exception point
  159. cacheLoad();
  160. // Drop browser node
  161. dropBrowser();
  162. // Notify objects
  163. desktop->broadcastObjChange(OBJ_SCRIPT | OBJMOD_DELETE, this, 0, 0);
  164. disassociated = 1;
  165. }
  166. void ScriptEdit::reassociate(TreeView* node) { start_func
  167. assert(disassociated);
  168. // Notify objects
  169. desktop->broadcastObjChange(OBJ_SCRIPT | OBJMOD_CREATE, this, 0, 0);
  170. // Browser node
  171. addToBrowser(node, 0);
  172. // Ensure written to file
  173. headerModified = contentModified = 1;
  174. disassociated = 0;
  175. }
  176. void ScriptEdit::setHeaderModified() { start_func
  177. if (!headerModified) headerModified = 1;
  178. }
  179. void ScriptEdit::setContentModified() { start_func
  180. if (!contentModified) {
  181. contentModified = 1;
  182. // No possibility of returning to cache
  183. delete cacheFile;
  184. cacheFile = NULL;
  185. }
  186. }
  187. void ScriptEdit::codeModifiedPre(ContentChangeType type, int firstRow, int numRows, EditBox* editbox, Window* srcWin) { start_func
  188. if (world) {
  189. if ((!inMiddleOfUndoBlock) && (type != SCRIPT_MODIFY_DONE)) {
  190. getWorldEdit()->undo.preUndoBlock();
  191. inMiddleOfUndoBlock = 1;
  192. }
  193. if (type == SCRIPT_MODIFY_LINE) {
  194. getWorldEdit()->undo.storeUndoScriptModify(id, source, firstRow, numRows, editbox, srcWin);
  195. }
  196. else if (type == SCRIPT_INSERT_LINES) {
  197. getWorldEdit()->undo.storeUndoScriptInsert(id, firstRow, numRows, editbox, srcWin);
  198. }
  199. else if (type == SCRIPT_REMOVE_LINES) {
  200. getWorldEdit()->undo.storeUndoScriptRemove(id, source, firstRow, numRows, editbox, srcWin);
  201. }
  202. else if ((type == SCRIPT_MODIFY_DONE) && (inMiddleOfUndoBlock)) {
  203. getWorldEdit()->undo.postUndoBlock();
  204. inMiddleOfUndoBlock = 0;
  205. }
  206. }
  207. }
  208. void ScriptEdit::codeModifiedPost(ContentChangeType type, int firstRow, int numRows, EditBox* editbox, Window* exWin) { start_func
  209. setHeaderModified();
  210. setContentModified();
  211. getWorldEdit()->setGlobalLinksModified();
  212. sourceModified = 1;
  213. if (type == SCRIPT_MODIFY_LINE) {
  214. desktop->broadcastObjChange(OBJ_SCRIPT | OBJMOD_LINE, this, firstRow, numRows, exWin);
  215. }
  216. else if (type == SCRIPT_INSERT_LINES) {
  217. desktop->broadcastObjChange(OBJ_SCRIPT | OBJMOD_INSERT, this, firstRow, numRows, exWin);
  218. }
  219. else if (type == SCRIPT_REMOVE_LINES) {
  220. desktop->broadcastObjChange(OBJ_SCRIPT | OBJMOD_REMOVE, this, firstRow, numRows, exWin);
  221. }
  222. }
  223. void ScriptEdit::dropBrowser() { start_func
  224. browserNode = NULL;
  225. }
  226. void ScriptEdit::addToBrowser(TreeView* node, int makeCurrent) { start_func
  227. browserNode = new TreeView(name, this, 0, treeviewEventWrap);
  228. browserNode->setIcon(scriptType == SCRIPT_NOTE ? 8 : (scriptType == SCRIPT_LIBRARY ? 16 : (defaultId ? 13 : 12)));
  229. node->insert(browserNode, makeCurrent);
  230. }
  231. int ScriptEdit::treeviewEvent(int code, int command, int check) { start_func
  232. if (check) {
  233. if (command == EDIT_DELETE) return Window::COMMAND_ENABLE;
  234. return Window::COMMAND_HIDE;
  235. }
  236. switch (command) {
  237. case LV_SELECT:
  238. openEditWindow();
  239. return 1;
  240. case LV_RCLICK:
  241. // @TODO: Should actually be a popup with this as an item
  242. propertiesDialog(desktop->findPreviousFocusWindow());
  243. return 1;
  244. case EDIT_DELETE:
  245. case LV_DELETE:
  246. // Handles any error conditions/cancellation for us
  247. assert(world);
  248. getWorldEdit()->deleteScript(this, desktop->findPreviousFocusWindow());
  249. return 1;
  250. }
  251. return 0;
  252. }
  253. int ScriptEdit::treeviewEventWrap(void* ptr, int code, int command, int check) { start_func
  254. return ((ScriptEdit*)ptr)->treeviewEvent(code, command, check);
  255. }
  256. void ScriptEdit::setName(const string& newName, Window* srcWin, Window* exWin) throw_Undo { start_func
  257. if (name != newName) {
  258. setHeaderModified();
  259. if (world) {
  260. getWorldEdit()->undo.storeUndoName(UndoBuffer::UNDO_SCRIPTNAME, id, name, newName, srcWin);
  261. world->deindexScript(this);
  262. }
  263. name = newName;
  264. nameL = newName;
  265. toLower(nameL);
  266. if (world) world->indexScript(this);
  267. if (browserNode) browserNode->changeName(newName);
  268. desktop->broadcastObjChange(OBJ_SCRIPT | OBJMOD_NAME, this, 0, 0, exWin);
  269. }
  270. }
  271. void ScriptEdit::setDefault(AnimGroup* newAnimgroup, TileSet* newTileset, int newId, Window* srcWin, Window* exWin) throw_Undo { start_func
  272. if ((newAnimgroup != defaultAnimgroup) || (newTileset != defaultTileset) || (newId != defaultId)) {
  273. assert(newId >= 0);
  274. if ((newAnimgroup) || (newTileset)) {
  275. assert(newId);
  276. if (newAnimgroup) assert(!newTileset);
  277. else assert(!newAnimgroup);
  278. }
  279. else assert(!newId);
  280. setHeaderModified();
  281. int aId = 0;
  282. int tId = 0;
  283. if (defaultAnimgroup) aId = defaultAnimgroup->getId();
  284. if (defaultTileset) tId = defaultTileset->getId();
  285. if (world) getWorldEdit()->undo.storeUndoScriptDefault(id, aId, tId, defaultId, srcWin);
  286. defaultAnimgroup = newAnimgroup;
  287. defaultTileset = newTileset;
  288. defaultId = newId;
  289. if (browserNode) browserNode->setIcon(scriptType == SCRIPT_NOTE ? 8 : (scriptType == SCRIPT_LIBRARY ? 16 : (defaultId ? 13 : 12)));
  290. desktop->broadcastObjChange(OBJ_SCRIPT | OBJMOD_DEFAULT, this, 0, 0, exWin);
  291. }
  292. }
  293. list<string>& ScriptEdit::getSource() { start_func
  294. return source;
  295. }
  296. void ScriptEdit::openEditWindow() { start_func
  297. try {
  298. ScriptEditor* editor = new ScriptEditor(&source, this); // Our exception point
  299. editor->runWindowed();
  300. }
  301. catch (FileException& e) {
  302. guiErrorBox(string(e.details), errorTitleFile);
  303. }
  304. }
  305. int ScriptEdit::propertiesDialog(Window* srcWin, Window* exWin) { start_func
  306. string newName = name;
  307. AnimGroup* newAnimgroup = defaultAnimgroup;
  308. TileSet* newTileset = defaultTileset;
  309. int newId = defaultId;
  310. int result;
  311. if (scriptType == SCRIPT_NOTE) {
  312. result = ScriptPropertiesDialog::createNotes()->run(&newName, NULL, NULL, NULL, this, getWorldEdit());
  313. }
  314. else if (scriptType == SCRIPT_LIBRARY) {
  315. result = ScriptPropertiesDialog::createLibrary()->run(&newName, NULL, NULL, NULL, this, getWorldEdit());
  316. }
  317. else {
  318. result = ScriptPropertiesDialog::createScript()->run(&newName, &newAnimgroup, &newTileset, &newId, this, getWorldEdit());
  319. }
  320. if (result) {
  321. try {
  322. markLock();
  323. }
  324. catch (FileException& e) {
  325. guiErrorBox(string(e.details), errorTitleFile);
  326. return 0;
  327. }
  328. try {
  329. if (world) getWorldEdit()->undo.preUndoBlock();
  330. setName(newName, srcWin, exWin);
  331. if (scriptType == SCRIPT_CODE) setDefault(newAnimgroup, newTileset, newId, srcWin, exWin);
  332. if (world) getWorldEdit()->undo.postUndoBlock();
  333. }
  334. catch (UndoException& e) {
  335. markUnlock();
  336. return 0;
  337. }
  338. markUnlock();
  339. return 1;
  340. }
  341. return 0;
  342. }
  343. int ScriptEdit::isHeaderModified() const { start_func
  344. return headerModified;
  345. }
  346. int ScriptEdit::isContentModified() const { start_func
  347. return contentModified;
  348. }
  349. Uint32 ScriptEdit::saveHeader(FileWrite* file) throw_File { start_func
  350. assert(world);
  351. assert(id);
  352. // clean if needed to based on modifieds- don't want to save
  353. // unclean data (this fixes memStatus)
  354. if (contentModified) {
  355. assert(!cached);
  356. cleanForCompile();
  357. }
  358. file->writeInt(id);
  359. file->writeStr(name);
  360. // @TODO: option to not save compilation results
  361. file->writeInt(contentModified ? memStatus : diskStatus);
  362. // @TODO: option to not save source
  363. file->writeInt(1);
  364. file->writeInt(scriptType);
  365. if (defaultAnimgroup) file->writeInt(defaultAnimgroup->getId());
  366. else file->writeInt(0);
  367. if (defaultTileset) file->writeInt(defaultTileset->getId());
  368. else file->writeInt(0);
  369. file->writeInt(defaultId);
  370. // @TODO: option to not save compilation results
  371. if (memStatus >= CODE_PARSED) {
  372. assert(functions);
  373. file->writeInt(functions->size());
  374. FunctionMap::iterator pos;
  375. FunctionMap::iterator end = functions->end();
  376. for (pos = functions->begin(); pos != end; ++pos) {
  377. string name = (*pos).first;
  378. file->writeStr(name);
  379. Function& func = (*pos).second;
  380. file->writeInt(func.numparam);
  381. if (func.numparam) {
  382. for (int param = 0; param < func.numparam; ++param) {
  383. file->writeInt(func.parameters[param].baseType);
  384. file->writeInt(func.parameters[param].subType);
  385. file->writeInt(func.parameters[param].flags);
  386. }
  387. }
  388. file->writeInt(func.returntype.baseType);
  389. file->writeInt(func.returntype.subType);
  390. file->writeInt(func.returntype.flags);
  391. file->writeInt(func.offset);
  392. }
  393. }
  394. return 1;
  395. }
  396. void ScriptEdit::saveContent(FileWrite* file) throw_File { start_func
  397. assert(world);
  398. assert(id);
  399. // clean if needed to based on modifieds- don't want to save
  400. // unclean data
  401. cleanForCompile();
  402. // @TODO: option to not save source
  403. file->writeInt(source.size());
  404. list<string>::iterator pos;
  405. list<string>::iterator end = source.end();
  406. for (pos = source.begin(); pos != end; ++pos) {
  407. file->writeStr(*pos);
  408. }
  409. // @TODO: option to not save compilation results
  410. if (memStatus >= CODE_COMPILED) {
  411. assert(bytecode);
  412. assert(links);
  413. file->writeIntBulk(bytecode, bytecode[0] + 2);
  414. file->writeInt(links->size());
  415. list<LinkEntry>::iterator pos;
  416. list<LinkEntry>::iterator end = links->end();
  417. for (pos = links->begin(); pos != end; ++pos) {
  418. LinkEntry& link = *pos;
  419. file->writeInt(link.type);
  420. file->writeInt(link.offset);
  421. file->writeStr(link.scope);
  422. file->writeStr(link.name);
  423. file->writeStr(link.wart);
  424. }
  425. }
  426. }
  427. void ScriptEdit::saveSuccess() { start_func
  428. headerModified = 0;
  429. contentModified = 0;
  430. diskStatus = memStatus;
  431. }
  432. void ScriptEdit::cachedContent(FileRead* file, int oldData) { start_func
  433. // If already cached..
  434. if (cached) {
  435. delete cacheFile;
  436. cacheFile = file;
  437. }
  438. else if (oldData) {
  439. delete file;
  440. }
  441. else {
  442. // Cache ourselves?
  443. if (!lockCount) {
  444. delete cacheFile;
  445. cacheFile = file;
  446. source.clear();
  447. delete[] bytecode;
  448. delete links;
  449. bytecode = NULL;
  450. links = NULL;
  451. cached = 1;
  452. }
  453. else {
  454. delete file;
  455. }
  456. }
  457. }