CategoriesEditor.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755
  1. /* CategoriesEditor.cpp
  2. *
  3. * Copyright (C) 1993-2013 David Weenink, 2008,2015-2018 Paul Boersma
  4. *
  5. * This code is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or (at
  8. * your option) any later version.
  9. *
  10. * This code is distributed in the hope that it will be useful, but
  11. * WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. * General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this work. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. /*
  19. djmw 1995
  20. djmw 19980225 repaired a memory leak, caused by wrong inheritance for
  21. CategoriesEditorInsert command.
  22. djmw 20020408 GPL
  23. djmw 20020408 Modified 'createMenus'
  24. djmw 20060111 Replaced Resources.h with Preferences.h
  25. djmw 20060328 Changed last argument to 0 in XtVaSetValues, XtVaGetValues and XtVaCreateManagedWidget
  26. for 64-bit compatibility.
  27. djmw 20070620 Latest modification.
  28. pb 20080320 split off Help menu
  29. pb 20080321 new Editor API
  30. djmw 20090107 Removed a bug in update that caused editor to crash on replace
  31. djmw 20090203 Removed potential crashes in CategoriesEditor<command>_create.
  32. djmw 20110304 Thing_new
  33. djmw 20111110 Use autostringvector
  34. */
  35. #define CategoriesEditor_TEXTMAXLENGTH 100
  36. #include "CategoriesEditor.h"
  37. #include "EditorM.h"
  38. Thing_implement (CategoriesEditor, Editor, 0);
  39. static const conststring32 CategoriesEditor_EMPTYLABEL = U"(empty)";
  40. static void menu_cb_help (CategoriesEditor /* me */, EDITOR_ARGS_DIRECT) {
  41. Melder_help (U"CategoriesEditor");
  42. }
  43. #pragma mark - Collection extensions
  44. /* Preconditions: */
  45. /* 1 <= (position [i], newpos) <= size; */
  46. /* newpos <= position [1] || newpos >= position [npos] */
  47. static void Ordered_moveItems (Ordered me, integer position [], integer npos, integer newpos) {
  48. integer pos, min = position [1], max = position [1];
  49. for (integer i = 2; i <= npos; i ++) {
  50. if (position [i] > max) {
  51. max = position [i];
  52. } else if (position [i] < min) {
  53. min = position [i];
  54. }
  55. }
  56. Melder_assert (min >= 1 && max <= my size && (newpos <= min || newpos >= max));
  57. autoNUMvector<Daata> tmp (1, npos);
  58. /*
  59. Move some data from `me` into `tmp`, in a different order.
  60. */
  61. for (integer i = 1; i <= npos; i ++) {
  62. tmp [i] = (Daata) my at [position [i]]; // dangle
  63. my at [position [i]] = nullptr; // undangle
  64. }
  65. // create a contiguous 'hole'
  66. if (newpos <= min) {
  67. pos = max;
  68. for (integer i = max; i >= newpos; i --) {
  69. if (my at [i])
  70. my at [pos --] = my at [i];
  71. }
  72. pos = newpos;
  73. } else {
  74. pos = min;
  75. for (integer i = min; i <= newpos; i ++) {
  76. if (my at [i])
  77. my at [pos ++] = my at [i];
  78. }
  79. pos = newpos - npos + 1;
  80. }
  81. // fill the 'hole'
  82. for (integer i = 1; i <= npos; i ++)
  83. my at [pos ++] = tmp [i];
  84. }
  85. /*
  86. static void OrderedOfString_replaceItemPos (Collection me, autoSimpleString data, integer pos) {
  87. if (pos < 1 || pos > my size) {
  88. return;
  89. }
  90. my replaceItem_move (data.move(), pos);
  91. }
  92. */
  93. /* Remove the item at position 'from' and insert it at position 'to'. */
  94. static void Ordered_moveItem (Ordered me, integer from, integer to) {
  95. if (from < 1 || from > my size) {
  96. from = my size;
  97. }
  98. if (to < 1 || to > my size) {
  99. to = my size;
  100. }
  101. if (from == to) {
  102. return;
  103. }
  104. Daata tmp = my at [from];
  105. if (from > to) {
  106. for (integer i = from; i > to; i --)
  107. my at [i] = my at [i - 1];
  108. } else {
  109. for (integer i = from; i < to; i ++)
  110. my at [i] = my at [i + 1];
  111. }
  112. my at [to] = tmp;
  113. }
  114. #pragma mark - Widget updates
  115. static void notifyNumberOfSelected (CategoriesEditor me) {
  116. autoMelderString tmp;
  117. integer posCount;
  118. autoNUMvector <integer> posList (GuiList_getSelectedPositions (my list, & posCount), 1); // waste
  119. if (posCount > 0)
  120. MelderString_append (&tmp, posCount, U" selection", (posCount > 1 ? U"s." : U"."));
  121. if (tmp.string)
  122. GuiLabel_setText (my outOfView, tmp.string);
  123. }
  124. static void updateUndoAndRedoMenuItems (CategoriesEditor me) {
  125. conststring32 commandName;
  126. /*
  127. * Menu item `Undo`.
  128. */
  129. bool undoItemIsSensitive = true;
  130. if (commandName = CommandHistory_commandName (my history.get(), 0), ! commandName) {
  131. commandName = U"nothing";
  132. undoItemIsSensitive = false;
  133. }
  134. GuiButton_setText (my undo, Melder_cat (U"Undo ", U"\"", commandName, U"\""));
  135. GuiThing_setSensitive (my undo, undoItemIsSensitive);
  136. /*
  137. * Menu item `Redo`.
  138. */
  139. bool redoItemIsSensitive = true;
  140. if (commandName = CommandHistory_commandName (my history.get(), 1), ! commandName) {
  141. commandName = U"nothing";
  142. redoItemIsSensitive = false;
  143. }
  144. GuiButton_setText (my redo, Melder_cat (U"Redo ", U"\"", commandName, U"\""));
  145. GuiThing_setSensitive (my redo, redoItemIsSensitive);
  146. }
  147. static void updateWidgets (CategoriesEditor me) { // all buttons except undo & redo
  148. Categories data = (Categories) my data;
  149. integer size = data->size;
  150. bool insert = false, insertAtEnd = true, replace = false, remove = false;
  151. bool moveUp = false, moveDown = false;
  152. integer posCount;
  153. autoNUMvector <integer> posList (GuiList_getSelectedPositions (my list, & posCount), 1);
  154. if (posList.peek()) {
  155. integer firstPos = posList[1], lastPos = posList[posCount];
  156. bool contiguous = ( lastPos - firstPos + 1 == posCount );
  157. moveUp = contiguous && firstPos > 1;
  158. moveDown = contiguous && lastPos < size;
  159. my position = firstPos;
  160. remove = true;
  161. replace = true;
  162. //insertAtEnd = false;
  163. if (posCount == 1) {
  164. insert = true;
  165. //if (posList[1] == size) insertAtEnd = true;
  166. if (size == 1 && str32equ (CategoriesEditor_EMPTYLABEL, data->at [1] -> string.get()))
  167. remove = false;
  168. }
  169. }
  170. GuiThing_setSensitive (my insert, insert);
  171. GuiThing_setSensitive (my insertAtEnd, insertAtEnd);
  172. GuiThing_setSensitive (my replace, replace);
  173. GuiThing_setSensitive (my remove, remove);
  174. GuiThing_setSensitive (my moveUp, moveUp);
  175. GuiThing_setSensitive (my moveDown, moveDown);
  176. if (my history) {
  177. updateUndoAndRedoMenuItems (me);
  178. }
  179. notifyNumberOfSelected (me);
  180. }
  181. static void update (CategoriesEditor me, integer from, integer to, const integer *select, integer nSelect) {
  182. Categories data = (Categories) my data;
  183. integer size = data->size;
  184. if (size == 0) {
  185. autoSimpleString str = SimpleString_create (CategoriesEditor_EMPTYLABEL);
  186. data -> addItem_move (str.move());
  187. update (me, 0, 0, nullptr, 0);
  188. return;
  189. }
  190. if (from == 0 && from == to)
  191. from = 1, to = size;
  192. if (from < 1 || from > size)
  193. from = size;
  194. if (to < 1 || to > size)
  195. to = size;
  196. if (from > to) {
  197. integer tmp = from;
  198. from = to;
  199. to = tmp;
  200. }
  201. // Begin optimization: add the items from a table instead of separately.
  202. try {
  203. integer offset = from - 1, numberOfElements = to - offset;
  204. autostring32vector table (numberOfElements);
  205. integer itemCount = GuiList_getNumberOfItems (my list);
  206. for (integer i = from; i <= to; i ++) {
  207. SimpleString category = data->at [i];
  208. table [i - offset] = Melder_dup_f (Melder_cat (i, U" ", category -> string.get()));
  209. }
  210. if (itemCount > size) { // have any items been removed from the Categories?
  211. for (integer j = itemCount; j > size; j --)
  212. GuiList_deleteItem (my list, j);
  213. itemCount = size;
  214. }
  215. if (to > itemCount) {
  216. for (integer j = 1; j <= to - itemCount; j ++)
  217. GuiList_insertItem (my list, table [itemCount + j - offset].get(), 0);
  218. }
  219. if (from <= itemCount) {
  220. integer n = (to < itemCount ? to : itemCount);
  221. for (integer j = from; j <= n; j ++)
  222. GuiList_replaceItem (my list, table [j - offset].get(), j);
  223. }
  224. } catch (MelderError) {
  225. throw;
  226. }
  227. // End of optimization
  228. // HIGHLIGHT
  229. GuiList_deselectAllItems (my list);
  230. if (size == 1) { /* the only item is always selected */
  231. SimpleString category = data->at [1];
  232. GuiList_selectItem (my list, 1);
  233. updateWidgets (me); // instead of "notify". BUG?
  234. GuiText_setString (my text, category -> string.get());
  235. } else if (nSelect > 0) {
  236. /*
  237. Select, but postpone highlighting.
  238. */
  239. for (integer i = 1; i <= nSelect; i ++)
  240. GuiList_selectItem (my list, select [i] > size ? size : select [i]);
  241. }
  242. // VIEWPORT
  243. {
  244. integer top = GuiList_getTopPosition (my list), bottom = GuiList_getBottomPosition (my list);
  245. integer visible = bottom - top + 1;
  246. if (nSelect == 0) {
  247. top = my position - visible / 2;
  248. } else if (select [nSelect] < top) {
  249. // selection above visible area
  250. top = select [1];
  251. } else if (select [1] > bottom) {
  252. // selection below visible area
  253. top = select [nSelect] - visible + 1;
  254. } else {
  255. integer deltaTopPos = -1, nUpdate = to - from + 1;
  256. if ((from == select [1] && to == select [nSelect]) || // replace
  257. (nUpdate > 2 && nSelect == 1)) // insert
  258. {
  259. deltaTopPos = 0;
  260. } else if (nUpdate == nSelect + 1 && select [1] == from + 1) { // down
  261. deltaTopPos = 1;
  262. }
  263. top += deltaTopPos;
  264. }
  265. if (top + visible > size) {
  266. top = size - visible + 1;
  267. }
  268. if (top < 1) {
  269. top = 1;
  270. }
  271. GuiList_setTopPosition (my list, top);
  272. }
  273. }
  274. #pragma mark - Commands for Undo and Redo
  275. Thing_define (CategoriesEditorCommand, Command) {
  276. autoCategories categories;
  277. integer *selection;
  278. integer nSelected, newPos;
  279. void v_destroy () noexcept
  280. override;
  281. };
  282. Thing_implement (CategoriesEditorCommand, Command, 0);
  283. void structCategoriesEditorCommand :: v_destroy () noexcept {
  284. NUMvector_free (selection, 1);
  285. CategoriesEditorCommand_Parent :: v_destroy ();
  286. }
  287. static void CategoriesEditorCommand_init (CategoriesEditorCommand me, conststring32 name, Thing boss,
  288. Command_Callback execute, Command_Callback undo, integer /*nCategories*/, integer nSelected)
  289. {
  290. my nSelected = nSelected;
  291. Command_init (me, name, boss, execute, undo);
  292. my categories = Categories_create();
  293. my selection = NUMvector <integer> (1, nSelected);
  294. }
  295. #pragma mark Insert
  296. Thing_define (CategoriesEditorInsert, CategoriesEditorCommand) {
  297. };
  298. Thing_implement (CategoriesEditorInsert, CategoriesEditorCommand, 0);
  299. static int CategoriesEditorInsert_execute (CategoriesEditorInsert me) {
  300. CategoriesEditor editor = static_cast<CategoriesEditor> (my boss);
  301. Categories categories = static_cast<Categories> (editor -> data);
  302. {// scope
  303. autoSimpleString str = Data_copy (my categories->at [1]);
  304. categories -> addItemAtPosition_move (str.move(), my selection [1]);
  305. }
  306. update (editor, my selection [1], 0, my selection, 1);
  307. return 1;
  308. }
  309. static int CategoriesEditorInsert_undo (CategoriesEditorInsert me) {
  310. CategoriesEditor editor = static_cast<CategoriesEditor> (my boss);
  311. Categories categories = static_cast<Categories> (editor -> data);
  312. categories -> removeItem (my selection [1]);
  313. update (editor, my selection [1], 0, my selection, 1);
  314. return 1;
  315. }
  316. static autoCategoriesEditorInsert CategoriesEditorInsert_create (Thing boss, autoSimpleString str, int position) {
  317. try {
  318. autoCategoriesEditorInsert me = Thing_new (CategoriesEditorInsert);
  319. CategoriesEditorCommand_init (me.get(), U"Insert", boss, CategoriesEditorInsert_execute, CategoriesEditorInsert_undo, 1, 1);
  320. my selection [1] = position;
  321. my categories -> addItem_move (str.move());
  322. return me;
  323. } catch (MelderError) {
  324. Melder_throw (U"CategoriesEditorInsert not created.");
  325. }
  326. }
  327. #pragma mark Remove
  328. Thing_define (CategoriesEditorRemove, CategoriesEditorCommand) {
  329. };
  330. Thing_implement (CategoriesEditorRemove, CategoriesEditorCommand, 0);
  331. static int CategoriesEditorRemove_execute (CategoriesEditorRemove me) {
  332. CategoriesEditor editor = static_cast<CategoriesEditor> (my boss);
  333. Categories categories = static_cast<Categories> (editor -> data);
  334. for (integer i = my nSelected; i >= 1; i--) {
  335. autoSimpleString item = Data_copy (categories->at [my selection [i]]); // FIXME this copy can probably be replaced with a move
  336. my categories -> addItemAtPosition_move (item.move(), 1);
  337. categories -> removeItem (my selection [i]);
  338. }
  339. update (editor, my selection [1], 0, nullptr, 0);
  340. return 1;
  341. }
  342. static int CategoriesEditorRemove_undo (CategoriesEditorRemove me) {
  343. CategoriesEditor editor = (CategoriesEditor) my boss;
  344. Categories categories = (Categories) editor -> data;
  345. for (integer i = 1; i <= my nSelected; i ++) {
  346. autoSimpleString item = Data_copy (my categories->at [i]);
  347. categories -> addItemAtPosition_move (item.move(), my selection [i]);
  348. }
  349. update (editor, my selection [1], 0, my selection, my nSelected);
  350. return 1;
  351. }
  352. static autoCategoriesEditorRemove CategoriesEditorRemove_create (Thing boss, integer *posList, integer posCount) {
  353. try {
  354. autoCategoriesEditorRemove me = Thing_new (CategoriesEditorRemove);
  355. CategoriesEditorCommand_init (me.get(), U"Remove", boss, CategoriesEditorRemove_execute,
  356. CategoriesEditorRemove_undo, posCount, posCount);
  357. for (integer i = 1; i <= posCount; i ++)
  358. my selection [i] = posList [i];
  359. return me;
  360. } catch (MelderError) {
  361. Melder_throw (U"CategoriesEditorRemove not created.");
  362. }
  363. }
  364. #pragma mark Replace
  365. Thing_define (CategoriesEditorReplace, CategoriesEditorCommand) {
  366. };
  367. Thing_implement (CategoriesEditorReplace, CategoriesEditorCommand, 0);
  368. static int CategoriesEditorReplace_execute (CategoriesEditorReplace me) {
  369. CategoriesEditor editor = static_cast<CategoriesEditor> (my boss);
  370. Categories categories = static_cast<Categories> (editor -> data);
  371. for (integer i = my nSelected; i >= 1; i --) {
  372. /*
  373. Swap categories->at [1] with categories->at [my selection [i]] under ambiguous ownership.
  374. */
  375. autoSimpleString tmp = Data_copy (my categories->at [1]);
  376. autoSimpleString other;
  377. other. adoptFromAmbiguousOwner (categories->at [my selection [i]]);
  378. my categories -> addItemAtPosition_move (other.move(), 2); // YUCK
  379. categories->at [my selection [i]] = tmp.releaseToAmbiguousOwner();
  380. }
  381. update (editor, my selection [1], my selection [my nSelected], my selection, my nSelected);
  382. return 1;
  383. }
  384. static int CategoriesEditorReplace_undo (CategoriesEditorReplace me) {
  385. CategoriesEditor editor = static_cast<CategoriesEditor> (my boss);
  386. Categories categories = static_cast<Categories> (editor -> data);
  387. for (integer i = 1; i <= my nSelected; i ++) {
  388. autoSimpleString str = Data_copy (my categories->at [i + 1]);
  389. categories -> replaceItem_move (str.move(), my selection [i]);
  390. }
  391. update (editor, my selection [1], my selection[my nSelected], my selection, my nSelected);
  392. return 1;
  393. }
  394. static autoCategoriesEditorReplace CategoriesEditorReplace_create (Thing boss, autoSimpleString str, integer *posList, integer posCount) {
  395. try {
  396. autoCategoriesEditorReplace me = Thing_new (CategoriesEditorReplace);
  397. CategoriesEditorCommand_init (me.get(), U"Replace", boss, CategoriesEditorReplace_execute,
  398. CategoriesEditorReplace_undo, posCount + 1, posCount);
  399. for (integer i = 1; i <= posCount; i ++)
  400. my selection [i] = posList [i];
  401. my categories -> addItem_move (str.move());
  402. return me;
  403. } catch (MelderError) {
  404. Melder_throw (U"CategoriesEditorReplace not created.");
  405. }
  406. }
  407. #pragma mark MoveUp
  408. Thing_define (CategoriesEditorMoveUp, CategoriesEditorCommand) {
  409. };
  410. Thing_implement (CategoriesEditorMoveUp, CategoriesEditorCommand, 0);
  411. static int CategoriesEditorMoveUp_execute (CategoriesEditorMoveUp me) {
  412. CategoriesEditor editor = static_cast<CategoriesEditor> (my boss);
  413. Categories categories = static_cast<Categories> (editor -> data);
  414. Ordered_moveItems ((Ordered) categories, my selection, my nSelected, my newPos); // FIXME cast
  415. autoNUMvector<integer> selection (1, my nSelected);
  416. for (integer i = 1; i <= my nSelected; i ++)
  417. selection[i] = my newPos + i - 1;
  418. update (editor, my newPos, my selection[my nSelected], selection.peek(), my nSelected);
  419. return 1;
  420. }
  421. static int CategoriesEditorMoveUp_undo (CategoriesEditorMoveUp me) {
  422. CategoriesEditor editor = static_cast<CategoriesEditor> (my boss);
  423. Categories categories = static_cast<Categories> (editor -> data);
  424. for (integer i = 1; i <= my nSelected; i ++)
  425. Ordered_moveItem ((Ordered) categories, my newPos, my selection [my nSelected]); // FIXME cast
  426. update (editor, my newPos, my selection[my nSelected], my selection, my nSelected);
  427. return 1;
  428. }
  429. static autoCategoriesEditorMoveUp CategoriesEditorMoveUp_create (Thing boss, integer *posList, integer posCount, integer newPos) {
  430. try {
  431. autoCategoriesEditorMoveUp me = Thing_new (CategoriesEditorMoveUp);
  432. CategoriesEditorCommand_init (me.get(), U"Move up", boss, CategoriesEditorMoveUp_execute, CategoriesEditorMoveUp_undo, 0, posCount);
  433. for (integer i = 1; i <= posCount; i ++)
  434. my selection [i] = posList [i];
  435. my newPos = newPos;
  436. return me;
  437. } catch (MelderError) {
  438. Melder_throw (U"CategoriesEditorMoveUp not created.");
  439. }
  440. }
  441. #pragma mark MoveDown
  442. Thing_define (CategoriesEditorMoveDown, CategoriesEditorCommand) {
  443. };
  444. Thing_implement (CategoriesEditorMoveDown, CategoriesEditorCommand, 0);
  445. static int CategoriesEditorMoveDown_execute (CategoriesEditorMoveDown me) {
  446. CategoriesEditor editor = static_cast<CategoriesEditor> (my boss);
  447. Categories categories = static_cast<Categories> (editor -> data);
  448. Ordered_moveItems ((Ordered) categories, my selection, my nSelected, my newPos); // FIXME cast
  449. autoNUMvector<integer> selection (1, my nSelected);
  450. for (integer i = 1; i <= my nSelected; i ++)
  451. selection [i] = my newPos - my nSelected + i;
  452. update (editor, my selection[1], my newPos, selection.peek(), my nSelected);
  453. return 1;
  454. }
  455. static int CategoriesEditorMoveDown_undo (CategoriesEditorMoveDown me) {
  456. CategoriesEditor editor = static_cast<CategoriesEditor> (my boss);
  457. Categories categories = static_cast<Categories> (editor -> data);
  458. for (integer i = 1; i <= my nSelected; i ++)
  459. Ordered_moveItem ((Ordered) categories, my newPos, my selection [1]); // TODO 1 or i ?? // FIXME cast
  460. integer from = my selection [1];
  461. update (editor, ( from > 1 ? from -- : from ), my newPos, my selection, my nSelected);
  462. return 1;
  463. }
  464. static autoCategoriesEditorMoveDown CategoriesEditorMoveDown_create (Thing boss, integer *posList,
  465. integer posCount, integer newPos) {
  466. try {
  467. autoCategoriesEditorMoveDown me = Thing_new (CategoriesEditorMoveDown);
  468. CategoriesEditorCommand_init (me.get(), U"Move down", boss, CategoriesEditorMoveDown_execute, CategoriesEditorMoveDown_undo, 0, posCount);
  469. for (integer i = 1; i <= posCount; i ++)
  470. my selection [i] = posList [i];
  471. my newPos = newPos;
  472. return me;
  473. } catch (MelderError) {
  474. Melder_throw (U"CategoriesEditorMoveDown not created.");
  475. }
  476. }
  477. #pragma mark - Callbacks
  478. static void gui_button_cb_remove (CategoriesEditor me, GuiButtonEvent /* event */) {
  479. integer posCount;
  480. autoNUMvector <integer> posList (GuiList_getSelectedPositions (my list, & posCount), 1);
  481. if (posList.peek()) {
  482. autoCategoriesEditorRemove command = CategoriesEditorRemove_create (me, posList.peek(), posCount);
  483. if (! Command_do (command.get()))
  484. return;
  485. if (my history)
  486. CommandHistory_insertItem_move (my history.get(), command.move());
  487. updateWidgets (me);
  488. }
  489. }
  490. static void insert (CategoriesEditor me, int position) {
  491. autostring32 text = GuiText_getString (my text);
  492. if (text && text [0] != U'\0') {
  493. autoSimpleString str = SimpleString_create (text.get());
  494. autoCategoriesEditorInsert command = CategoriesEditorInsert_create (me, str.move(), position);
  495. Command_do (command.get());
  496. if (my history)
  497. CommandHistory_insertItem_move (my history.get(), command.move());
  498. updateWidgets (me);
  499. }
  500. }
  501. static void gui_button_cb_insert (CategoriesEditor me, GuiButtonEvent /* event */) {
  502. insert (me, my position);
  503. }
  504. static void gui_button_cb_insertAtEnd (CategoriesEditor me, GuiButtonEvent /* event */) {
  505. Categories categories = (Categories) my data;
  506. insert (me, categories->size + 1);
  507. my position = categories->size;
  508. }
  509. static void gui_button_cb_replace (CategoriesEditor me, GuiButtonEvent /* event */) {
  510. integer posCount;
  511. autoNUMvector <integer> posList (GuiList_getSelectedPositions (my list, & posCount), 1);
  512. if (posCount > 0) {
  513. autostring32 text = GuiText_getString (my text);
  514. if (text && text [0] != U'\0') {
  515. autoSimpleString str = SimpleString_create (text.get());
  516. autoCategoriesEditorReplace command = CategoriesEditorReplace_create (me, str.move(), posList.peek(), posCount);
  517. Command_do (command.get());
  518. if (my history)
  519. CommandHistory_insertItem_move (my history.get(), command.move());
  520. updateWidgets (me);
  521. }
  522. }
  523. }
  524. /* Precondition: contiguous selection */
  525. static void gui_button_cb_moveUp (CategoriesEditor me, GuiButtonEvent /* event */) {
  526. integer posCount;
  527. autoNUMvector <integer> posList (GuiList_getSelectedPositions (my list, & posCount), 1);
  528. if (posCount > 0) {
  529. autoCategoriesEditorMoveUp command = CategoriesEditorMoveUp_create (me, posList.peek(), posCount, posList[1] - 1);
  530. Command_do (command.get());
  531. if (my history)
  532. CommandHistory_insertItem_move (my history.get(), command.move());
  533. updateWidgets (me);
  534. }
  535. }
  536. /* Precondition: contiguous selection */
  537. static void gui_button_cb_moveDown (CategoriesEditor me, GuiButtonEvent /* event */) {
  538. integer posCount;
  539. autoNUMvector <integer> posList (GuiList_getSelectedPositions (my list, & posCount), 1);
  540. if (posCount > 0) {
  541. autoCategoriesEditorMoveDown command = CategoriesEditorMoveDown_create (me, posList.peek(), posCount, posList[posCount] + 1);
  542. Command_do (command.get());
  543. if (my history)
  544. CommandHistory_insertItem_move (my history.get(), command.move());
  545. updateWidgets (me);
  546. }
  547. }
  548. static void gui_list_cb_selectionChanged (CategoriesEditor me, GuiList_SelectionChangedEvent /* event */) {
  549. updateWidgets (me);
  550. }
  551. static void gui_list_cb_doubleClick (CategoriesEditor me, GuiList_DoubleClickEvent event) {
  552. Melder_assert (event -> list == my list);
  553. Categories data = (Categories) my data;
  554. // `my position` should just have been updated by the selectionChanged callback.
  555. integer posCount;
  556. autoNUMvector <integer> posList (GuiList_getSelectedPositions (my list, & posCount), 1);
  557. if (posCount == 1 // often or even usually true when double-clicking?
  558. && posList [1] == my position) // should be true, but we don't crash if it's false
  559. {
  560. SimpleString category = data->at [my position];
  561. GuiText_setString (my text, category -> string ? category -> string.get() : U"");
  562. }
  563. }
  564. static void gui_list_cb_scroll (CategoriesEditor me, GuiList_ScrollEvent /* event */) {
  565. notifyNumberOfSelected (me);
  566. }
  567. static void gui_button_cb_undo (CategoriesEditor me, GuiButtonEvent /* event */) {
  568. if (CommandHistory_offleft (my history.get()))
  569. return;
  570. Command command = CommandHistory_getItem (my history.get());
  571. Command_undo (command);
  572. CommandHistory_back (my history.get());
  573. updateWidgets (me);
  574. }
  575. static void gui_button_cb_redo (CategoriesEditor me, GuiButtonEvent /* event */) {
  576. CommandHistory_forth (my history.get());
  577. if (CommandHistory_offright (my history.get()))
  578. return;
  579. Command command = CommandHistory_getItem (my history.get());
  580. Command_do (command);
  581. updateWidgets (me);
  582. }
  583. #pragma mark - Editor methods
  584. void structCategoriesEditor :: v_destroy () noexcept {
  585. CategoriesEditor_Parent :: v_destroy ();
  586. }
  587. void structCategoriesEditor :: v_createHelpMenuItems (EditorMenu menu) {
  588. CategoriesEditor_Parent :: v_createHelpMenuItems (menu);
  589. EditorMenu_addCommand (menu, U"CategoriesEditor help", '?', menu_cb_help);
  590. }
  591. // origin is at top left.
  592. void structCategoriesEditor :: v_createChildren () {
  593. constexpr int menuBarOffset { 40 };
  594. constexpr int button_width { 130 }, button_height { menuBarOffset }, list_width { 260 }, list_height { 420 };
  595. constexpr int delta_x { 15 }, delta_y { menuBarOffset / 2 }, text_button_height { button_height / 2 };
  596. int left = 5, right = left + button_width, top = 3 + menuBarOffset, bottom = top + text_button_height;
  597. GuiLabel_createShown (our windowForm, left, right, top, bottom, U"Positions:", 0);
  598. left = right + delta_x ; right = left + button_width;
  599. GuiLabel_createShown (our windowForm, left, right, top, bottom, U"Values:", 0);
  600. left = 0; right = left + list_width;
  601. // int buttons_top = (top = bottom + delta_y);
  602. int list_bottom = bottom = top + list_height;
  603. list = GuiList_create (our windowForm, left, right, top, bottom, true, 0);
  604. GuiList_setSelectionChangedCallback (list, gui_list_cb_selectionChanged, this);
  605. GuiList_setDoubleClickCallback (list, gui_list_cb_doubleClick, this);
  606. GuiList_setScrollCallback (list, gui_list_cb_scroll, this);
  607. GuiThing_show (list);
  608. int buttons_left = left = right + 2 * delta_x; right = left + button_width; bottom = top + button_height;
  609. GuiLabel_createShown (our windowForm, left, right, top, bottom, U"Value:", 0);
  610. left = right + delta_x; right = left + button_width;
  611. text = GuiText_createShown (our windowForm, left, right, top, bottom, 0);
  612. GuiText_setString (text, CategoriesEditor_EMPTYLABEL);
  613. left = buttons_left; right = left + button_width; top = bottom + delta_y; bottom = top + button_height;
  614. insert = GuiButton_createShown (our windowForm, left, right, top, bottom, U"Insert", gui_button_cb_insert, this, GuiButton_DEFAULT);
  615. left = right + delta_x; right = left + button_width;
  616. replace = GuiButton_createShown (our windowForm, left, right, top, bottom, U"Replace", gui_button_cb_replace, this, 0);
  617. left = buttons_left; right = left + int (1.5 * button_width); top = bottom + delta_y; bottom = top + button_height;
  618. insertAtEnd = GuiButton_createShown (our windowForm, left, right, top, bottom, U"Insert at end", gui_button_cb_insertAtEnd, this, 0);
  619. top = bottom + delta_y; bottom = top + button_height;
  620. undo = GuiButton_createShown (our windowForm, left, right, top, bottom, U"Undo", gui_button_cb_undo, this, 0);
  621. top = bottom + delta_y; bottom = top + button_height;
  622. redo = GuiButton_createShown (our windowForm, left, right, top, bottom, U"Redo", gui_button_cb_redo, this, 0);
  623. top = bottom + delta_y; bottom = top + button_height;
  624. remove = GuiButton_createShown (our windowForm, left, right, top, bottom, U"Remove", gui_button_cb_remove, this, 0);
  625. top = bottom + delta_y; bottom = top + button_height;
  626. moveUp = GuiButton_createShown (our windowForm, left, right, top, bottom, U"Move selection up", gui_button_cb_moveUp, this, 0);
  627. top = bottom + delta_y; bottom = top + button_height;
  628. moveDown = GuiButton_createShown (our windowForm, left, right, top, bottom, U"Move selection down", gui_button_cb_moveDown, this, 0);
  629. top = list_bottom + delta_y; bottom = top + button_height; left = 5; right = left + 200;
  630. outOfView = GuiLabel_createShown (our windowForm, left, right, top, bottom, U"", 0);
  631. }
  632. void structCategoriesEditor :: v_dataChanged () {
  633. update (this, 0, 0, nullptr, 0);
  634. updateWidgets (this);
  635. }
  636. #pragma mark -
  637. autoCategoriesEditor CategoriesEditor_create (conststring32 title, Categories data) {
  638. try {
  639. autoCategoriesEditor me = Thing_new (CategoriesEditor);
  640. Editor_init (me.get(), 20, 40, 600, 600, title, data);
  641. my history = CommandHistory_create ();
  642. update (me.get(), 0, 0, nullptr, 0);
  643. updateWidgets (me.get());
  644. return me;
  645. } catch (MelderError) {
  646. Melder_throw (U"Categories window not created.");
  647. }
  648. }
  649. /* End of file CategoriesEditor.cpp */