TextEditor.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704
  1. /* TextEditor.cpp
  2. *
  3. * Copyright (C) 1997-2018 Paul Boersma, 2010 Franz Brausse
  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.
  13. * See the GNU 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. #include "TextEditor.h"
  19. #include "machine.h"
  20. #include "../kar/longchar.h"
  21. #include "EditorM.h"
  22. #include "../kar/UnicodeData.h"
  23. Thing_implement (TextEditor, Editor, 0);
  24. #include "prefs_define.h"
  25. #include "TextEditor_prefs.h"
  26. #include "prefs_install.h"
  27. #include "TextEditor_prefs.h"
  28. #include "prefs_copyToInstance.h"
  29. #include "TextEditor_prefs.h"
  30. static CollectionOf <structTextEditor> theReferencesToAllOpenTextEditors;
  31. /***** TextEditor methods *****/
  32. void structTextEditor :: v_destroy () noexcept {
  33. our openDialog.reset(); // don't delay till delete
  34. our saveDialog.reset(); // don't delay till delete
  35. theReferencesToAllOpenTextEditors. undangleItem (this);
  36. TextEditor_Parent :: v_destroy ();
  37. }
  38. void structTextEditor :: v_nameChanged () {
  39. if (v_fileBased ()) {
  40. bool dirtinessAlreadyShown = GuiWindow_setDirty (our windowForm, our dirty);
  41. static MelderString windowTitle { };
  42. if (our name [0] == U'\0') {
  43. MelderString_copy (& windowTitle, U"(untitled");
  44. if (dirty && ! dirtinessAlreadyShown)
  45. MelderString_append (& windowTitle, U", modified");
  46. MelderString_append (& windowTitle, U")");
  47. } else {
  48. MelderString_copy (& windowTitle, U"File ", MelderFile_messageName (& our file));
  49. if (dirty && ! dirtinessAlreadyShown)
  50. MelderString_append (& windowTitle, U" (modified)");
  51. }
  52. GuiShell_setTitle (our windowForm, windowTitle.string);
  53. //MelderString_copy (& windowTitle, our dirty && ! dirtinessAlreadyShown ? U"*" : U"", our name [0] == U'\0' ? U"(untitled)" : MelderFile_name (& our file));
  54. } else {
  55. TextEditor_Parent :: v_nameChanged ();
  56. }
  57. }
  58. static void openDocument (TextEditor me, MelderFile file) {
  59. for (integer ieditor = 1; ieditor <= theReferencesToAllOpenTextEditors.size; ieditor ++) {
  60. TextEditor editor = theReferencesToAllOpenTextEditors.at [ieditor];
  61. if (editor != me && MelderFile_equal (file, & editor -> file)) {
  62. Editor_raise (editor);
  63. Melder_appendError (U"Text file ", file, U" is already open.");
  64. forget (me); // don't forget me before Melder_appendError, because "file" is owned by one of my dialogs
  65. Melder_flushError ();
  66. return;
  67. }
  68. }
  69. autostring32 text = MelderFile_readText (file);
  70. GuiText_setString (my textWidget, text.get());
  71. /*
  72. * GuiText_setString has invoked the changeCallback,
  73. * which has set `my dirty` to `true`. Fix this.
  74. */
  75. my dirty = false;
  76. MelderFile_copy (file, & my file);
  77. Thing_setName (me, Melder_fileToPath (file));
  78. }
  79. static void newDocument (TextEditor me) {
  80. GuiText_setString (my textWidget, U""); // implicitly sets my dirty to `true`
  81. my dirty = false;
  82. if (my v_fileBased ()) Thing_setName (me, U"");
  83. }
  84. static void saveDocument (TextEditor me, MelderFile file) {
  85. autostring32 text = GuiText_getString (my textWidget);
  86. MelderFile_writeText (file, text.get(), Melder_getOutputEncoding ());
  87. my dirty = false;
  88. MelderFile_copy (file, & my file);
  89. if (my v_fileBased ()) Thing_setName (me, Melder_fileToPath (file));
  90. }
  91. static void closeDocument (TextEditor me) {
  92. forget (me);
  93. }
  94. static void cb_open_ok (UiForm sendingForm, integer /* narg */, Stackel /* args */, conststring32 /* sendingString */,
  95. Interpreter /* interpreter */, conststring32 /* invokingButtonTitle */, bool /* modified */, void *void_me)
  96. {
  97. iam (TextEditor);
  98. MelderFile file = UiFile_getFile (sendingForm);
  99. openDocument (me, file);
  100. }
  101. static void cb_showOpen (EditorCommand cmd) {
  102. TextEditor me = (TextEditor) cmd -> d_editor;
  103. if (! my openDialog)
  104. my openDialog = UiInfile_create (my windowForm, U"Open", cb_open_ok, me, nullptr, nullptr, false);
  105. UiInfile_do (my openDialog.get());
  106. }
  107. static void cb_saveAs_ok (UiForm sendingForm, integer /* narg */, Stackel /* args */, conststring32 /* sendingString */,
  108. Interpreter /* interpreter */, conststring32 /* invokingButtonTitle */, bool /* modified */, void *void_me)
  109. {
  110. iam (TextEditor);
  111. MelderFile file = UiFile_getFile (sendingForm);
  112. saveDocument (me, file);
  113. }
  114. static void menu_cb_saveAs (TextEditor me, EDITOR_ARGS_DIRECT) {
  115. if (! my saveDialog)
  116. my saveDialog = UiOutfile_create (my windowForm, U"Save", cb_saveAs_ok, me, nullptr, nullptr);
  117. char32 defaultName [300];
  118. Melder_sprint (defaultName,300, ! my v_fileBased () ? U"info.txt" : my name [0] ? MelderFile_name (& my file) : U"");
  119. UiOutfile_do (my saveDialog.get(), defaultName);
  120. }
  121. static void gui_button_cb_saveAndOpen (EditorCommand cmd, GuiButtonEvent /* event */) {
  122. TextEditor me = (TextEditor) cmd -> d_editor;
  123. GuiThing_hide (my dirtyOpenDialog);
  124. if (my name [0]) {
  125. try {
  126. saveDocument (me, & my file);
  127. } catch (MelderError) {
  128. Melder_flushError ();
  129. return;
  130. }
  131. cb_showOpen (cmd);
  132. } else {
  133. menu_cb_saveAs (me, cmd, nullptr, 0, nullptr, nullptr, nullptr);
  134. }
  135. }
  136. static void gui_button_cb_cancelOpen (EditorCommand cmd, GuiButtonEvent /* event */) {
  137. TextEditor me = (TextEditor) cmd -> d_editor;
  138. GuiThing_hide (my dirtyOpenDialog);
  139. }
  140. static void gui_button_cb_discardAndOpen (EditorCommand cmd, GuiButtonEvent /* event */) {
  141. TextEditor me = (TextEditor) cmd -> d_editor;
  142. GuiThing_hide (my dirtyOpenDialog);
  143. cb_showOpen (cmd);
  144. }
  145. static void menu_cb_open (TextEditor me, EDITOR_ARGS_CMD) {
  146. if (my dirty) {
  147. if (! my dirtyOpenDialog) {
  148. int buttonWidth = 120, buttonSpacing = 20;
  149. my dirtyOpenDialog = GuiDialog_create (my windowForm,
  150. 150, 70,
  151. Gui_LEFT_DIALOG_SPACING + 3 * buttonWidth + 2 * buttonSpacing + Gui_RIGHT_DIALOG_SPACING,
  152. Gui_TOP_DIALOG_SPACING + Gui_TEXTFIELD_HEIGHT + Gui_VERTICAL_DIALOG_SPACING_SAME + 2 * Gui_BOTTOM_DIALOG_SPACING + Gui_PUSHBUTTON_HEIGHT,
  153. U"Text changed", nullptr, nullptr, GuiDialog_MODAL);
  154. GuiLabel_createShown (my dirtyOpenDialog,
  155. Gui_LEFT_DIALOG_SPACING, - Gui_RIGHT_DIALOG_SPACING,
  156. Gui_TOP_DIALOG_SPACING, Gui_TOP_DIALOG_SPACING + Gui_LABEL_HEIGHT,
  157. U"The text has changed! Save changes?", 0);
  158. int x = Gui_LEFT_DIALOG_SPACING, y = - Gui_BOTTOM_DIALOG_SPACING;
  159. GuiButton_createShown (my dirtyOpenDialog,
  160. x, x + buttonWidth, y - Gui_PUSHBUTTON_HEIGHT, y,
  161. U"Discard & Open", gui_button_cb_discardAndOpen, cmd, 0);
  162. x += buttonWidth + buttonSpacing;
  163. GuiButton_createShown (my dirtyOpenDialog,
  164. x, x + buttonWidth, y - Gui_PUSHBUTTON_HEIGHT, y,
  165. U"Cancel", gui_button_cb_cancelOpen, cmd, 0);
  166. x += buttonWidth + buttonSpacing;
  167. GuiButton_createShown (my dirtyOpenDialog,
  168. x, x + buttonWidth, y - Gui_PUSHBUTTON_HEIGHT, y,
  169. U"Save & Open", gui_button_cb_saveAndOpen, cmd, 0);
  170. }
  171. GuiThing_show (my dirtyOpenDialog);
  172. } else {
  173. cb_showOpen (cmd);
  174. }
  175. }
  176. static void gui_button_cb_saveAndNew (EditorCommand cmd, GuiButtonEvent /* event */) {
  177. TextEditor me = (TextEditor) cmd -> d_editor;
  178. GuiThing_hide (my dirtyNewDialog);
  179. if (my name [0]) {
  180. try {
  181. saveDocument (me, & my file);
  182. } catch (MelderError) {
  183. Melder_flushError ();
  184. return;
  185. }
  186. newDocument (me);
  187. } else {
  188. menu_cb_saveAs (me, cmd, nullptr, 0, nullptr, nullptr, nullptr);
  189. }
  190. }
  191. static void gui_button_cb_cancelNew (EditorCommand cmd, GuiButtonEvent /* event */) {
  192. TextEditor me = (TextEditor) cmd -> d_editor;
  193. GuiThing_hide (my dirtyNewDialog);
  194. }
  195. static void gui_button_cb_discardAndNew (EditorCommand cmd, GuiButtonEvent /* event */) {
  196. TextEditor me = (TextEditor) cmd -> d_editor;
  197. GuiThing_hide (my dirtyNewDialog);
  198. newDocument (me);
  199. }
  200. static void menu_cb_new (TextEditor me, EDITOR_ARGS_CMD) {
  201. if (my v_fileBased () && my dirty) {
  202. if (! my dirtyNewDialog) {
  203. int buttonWidth = 120, buttonSpacing = 20;
  204. my dirtyNewDialog = GuiDialog_create (my windowForm,
  205. 150, 70, Gui_LEFT_DIALOG_SPACING + 3 * buttonWidth + 2 * buttonSpacing + Gui_RIGHT_DIALOG_SPACING,
  206. Gui_TOP_DIALOG_SPACING + Gui_TEXTFIELD_HEIGHT + Gui_VERTICAL_DIALOG_SPACING_SAME + 2 * Gui_BOTTOM_DIALOG_SPACING + Gui_PUSHBUTTON_HEIGHT,
  207. U"Text changed", nullptr, nullptr, GuiDialog_MODAL);
  208. GuiLabel_createShown (my dirtyNewDialog,
  209. Gui_LEFT_DIALOG_SPACING, - Gui_RIGHT_DIALOG_SPACING,
  210. Gui_TOP_DIALOG_SPACING, Gui_TOP_DIALOG_SPACING + Gui_LABEL_HEIGHT,
  211. U"The text has changed! Save changes?", 0);
  212. int x = Gui_LEFT_DIALOG_SPACING, y = - Gui_BOTTOM_DIALOG_SPACING;
  213. GuiButton_createShown (my dirtyNewDialog,
  214. x, x + buttonWidth, y - Gui_PUSHBUTTON_HEIGHT, y,
  215. U"Discard & New", gui_button_cb_discardAndNew, cmd, 0);
  216. x += buttonWidth + buttonSpacing;
  217. GuiButton_createShown (my dirtyNewDialog,
  218. x, x + buttonWidth, y - Gui_PUSHBUTTON_HEIGHT, y,
  219. U"Cancel", gui_button_cb_cancelNew, cmd, 0);
  220. x += buttonWidth + buttonSpacing;
  221. GuiButton_createShown (my dirtyNewDialog,
  222. x, x + buttonWidth, y - Gui_PUSHBUTTON_HEIGHT, y,
  223. U"Save & New", gui_button_cb_saveAndNew, cmd, 0);
  224. }
  225. GuiThing_show (my dirtyNewDialog);
  226. } else {
  227. newDocument (me);
  228. }
  229. }
  230. static void gui_button_cb_cancelReopen (EditorCommand cmd, GuiButtonEvent /* event */) {
  231. TextEditor me = (TextEditor) cmd -> d_editor;
  232. GuiThing_hide (my dirtyReopenDialog);
  233. }
  234. static void gui_button_cb_discardAndReopen (EditorCommand cmd, GuiButtonEvent /* event */) {
  235. TextEditor me = (TextEditor) cmd -> d_editor;
  236. GuiThing_hide (my dirtyReopenDialog);
  237. openDocument (me, & my file);
  238. }
  239. static void menu_cb_reopen (TextEditor me, EDITOR_ARGS_CMD) {
  240. Melder_assert (my v_fileBased());
  241. if (my name [0] == U'\0') {
  242. Melder_throw (U"Cannot reopen from disk, because the text has never been saved yet.");
  243. }
  244. if (my dirty) {
  245. if (! my dirtyReopenDialog) {
  246. int buttonWidth = 250, buttonSpacing = 20;
  247. my dirtyReopenDialog = GuiDialog_create (my windowForm,
  248. 150, 70, Gui_LEFT_DIALOG_SPACING + 2 * buttonWidth + 1 * buttonSpacing + Gui_RIGHT_DIALOG_SPACING,
  249. Gui_TOP_DIALOG_SPACING + Gui_TEXTFIELD_HEIGHT + Gui_VERTICAL_DIALOG_SPACING_SAME + 2 * Gui_BOTTOM_DIALOG_SPACING + Gui_PUSHBUTTON_HEIGHT,
  250. U"Text changed", nullptr, nullptr, GuiDialog_MODAL);
  251. GuiLabel_createShown (my dirtyReopenDialog,
  252. Gui_LEFT_DIALOG_SPACING, - Gui_RIGHT_DIALOG_SPACING,
  253. Gui_TOP_DIALOG_SPACING, Gui_TOP_DIALOG_SPACING + Gui_LABEL_HEIGHT,
  254. U"The text in the editor contains changes! Reopen nevertheless?", 0);
  255. int x = Gui_LEFT_DIALOG_SPACING, y = - Gui_BOTTOM_DIALOG_SPACING;
  256. GuiButton_createShown (my dirtyReopenDialog,
  257. x, x + buttonWidth, y - Gui_PUSHBUTTON_HEIGHT, y,
  258. U"Keep visible version", gui_button_cb_cancelReopen, cmd, GuiButton_CANCEL);
  259. x += buttonWidth + buttonSpacing;
  260. GuiButton_createShown (my dirtyReopenDialog,
  261. x, x + buttonWidth, y - Gui_PUSHBUTTON_HEIGHT, y,
  262. U"Replace with version from disk", gui_button_cb_discardAndReopen, cmd, GuiButton_DEFAULT);
  263. }
  264. GuiThing_show (my dirtyReopenDialog);
  265. } else {
  266. try {
  267. openDocument (me, & my file);
  268. } catch (MelderError) {
  269. Melder_flushError ();
  270. return;
  271. }
  272. }
  273. }
  274. static void menu_cb_clear (TextEditor me, EDITOR_ARGS_DIRECT) {
  275. my v_clear ();
  276. }
  277. static void menu_cb_save (TextEditor me, EDITOR_ARGS_CMD) {
  278. if (my name [0]) {
  279. try {
  280. saveDocument (me, & my file);
  281. } catch (MelderError) {
  282. Melder_flushError ();
  283. return;
  284. }
  285. } else {
  286. menu_cb_saveAs (me, cmd, nullptr, 0, nullptr, nullptr, nullptr);
  287. }
  288. }
  289. static void gui_button_cb_saveAndClose (TextEditor me, GuiButtonEvent /* event */) {
  290. GuiThing_hide (my dirtyCloseDialog);
  291. if (my name [0]) {
  292. try {
  293. saveDocument (me, & my file);
  294. } catch (MelderError) {
  295. Melder_flushError ();
  296. return;
  297. }
  298. closeDocument (me);
  299. } else {
  300. menu_cb_saveAs (me, Editor_getMenuCommand (me, U"File", U"Save as..."), nullptr, 0, nullptr, nullptr, nullptr);
  301. }
  302. }
  303. static void gui_button_cb_cancelClose (TextEditor me, GuiButtonEvent /* event */) {
  304. GuiThing_hide (my dirtyCloseDialog);
  305. }
  306. static void gui_button_cb_discardAndClose (TextEditor me, GuiButtonEvent /* event */) {
  307. GuiThing_hide (my dirtyCloseDialog);
  308. closeDocument (me);
  309. }
  310. void structTextEditor :: v_goAway () {
  311. if (v_fileBased () && dirty) {
  312. if (! dirtyCloseDialog) {
  313. int buttonWidth = 120, buttonSpacing = 20;
  314. dirtyCloseDialog = GuiDialog_create (our windowForm,
  315. 150, 70, Gui_LEFT_DIALOG_SPACING + 3 * buttonWidth + 2 * buttonSpacing + Gui_RIGHT_DIALOG_SPACING,
  316. Gui_TOP_DIALOG_SPACING + Gui_TEXTFIELD_HEIGHT + Gui_VERTICAL_DIALOG_SPACING_SAME + 2 * Gui_BOTTOM_DIALOG_SPACING + Gui_PUSHBUTTON_HEIGHT,
  317. U"Text changed", nullptr, nullptr, GuiDialog_MODAL);
  318. GuiLabel_createShown (dirtyCloseDialog,
  319. Gui_LEFT_DIALOG_SPACING, - Gui_RIGHT_DIALOG_SPACING,
  320. Gui_TOP_DIALOG_SPACING, Gui_TOP_DIALOG_SPACING + Gui_LABEL_HEIGHT,
  321. U"The text has changed! Save changes?", 0);
  322. int x = Gui_LEFT_DIALOG_SPACING, y = - Gui_BOTTOM_DIALOG_SPACING;
  323. GuiButton_createShown (dirtyCloseDialog,
  324. x, x + buttonWidth, y - Gui_PUSHBUTTON_HEIGHT, y,
  325. U"Discard & Close", gui_button_cb_discardAndClose, this, 0);
  326. x += buttonWidth + buttonSpacing;
  327. GuiButton_createShown (dirtyCloseDialog,
  328. x, x + buttonWidth, y - Gui_PUSHBUTTON_HEIGHT, y,
  329. U"Cancel", gui_button_cb_cancelClose, this, 0);
  330. x += buttonWidth + buttonSpacing;
  331. GuiButton_createShown (dirtyCloseDialog,
  332. x, x + buttonWidth, y - Gui_PUSHBUTTON_HEIGHT, y,
  333. U"Save & Close", gui_button_cb_saveAndClose, this, 0);
  334. }
  335. if (our dirtyNewDialog) GuiThing_hide (our dirtyNewDialog);
  336. if (our dirtyOpenDialog) GuiThing_hide (our dirtyOpenDialog);
  337. if (our dirtyReopenDialog) GuiThing_hide (our dirtyReopenDialog);
  338. GuiThing_show (dirtyCloseDialog);
  339. } else {
  340. closeDocument (this);
  341. }
  342. }
  343. static void menu_cb_undo (TextEditor me, EDITOR_ARGS_DIRECT) {
  344. GuiText_undo (my textWidget);
  345. }
  346. static void menu_cb_redo (TextEditor me, EDITOR_ARGS_DIRECT) {
  347. GuiText_redo (my textWidget);
  348. }
  349. static void menu_cb_cut (TextEditor me, EDITOR_ARGS_DIRECT) {
  350. GuiText_cut (my textWidget); // use ((XmAnyCallbackStruct *) call) -> event -> xbutton. time
  351. }
  352. static void menu_cb_copy (TextEditor me, EDITOR_ARGS_DIRECT) {
  353. GuiText_copy (my textWidget);
  354. }
  355. static void menu_cb_paste (TextEditor me, EDITOR_ARGS_DIRECT) {
  356. GuiText_paste (my textWidget);
  357. }
  358. static void menu_cb_erase (TextEditor me, EDITOR_ARGS_DIRECT) {
  359. GuiText_remove (my textWidget);
  360. }
  361. static bool getSelectedLines (TextEditor me, integer *firstLine, integer *lastLine) {
  362. integer left, right;
  363. autostring32 text = GuiText_getStringAndSelectionPosition (my textWidget, & left, & right);
  364. integer textLength = str32len (text.get());
  365. Melder_assert (left >= 0);
  366. Melder_assert (left <= right);
  367. Melder_assert (right <= textLength);
  368. integer i = 0;
  369. *firstLine = 1;
  370. /*
  371. * Cycle through the text in order to see how many linefeeds we pass.
  372. */
  373. for (; i < left; i ++) {
  374. if (text [i] == U'\n') {
  375. (*firstLine) ++;
  376. }
  377. }
  378. if (left == right) return false;
  379. *lastLine = *firstLine;
  380. for (; i < right; i ++) {
  381. if (text [i] == U'\n') {
  382. (*lastLine) ++;
  383. }
  384. }
  385. return true;
  386. }
  387. static autostring32 theFindString, theReplaceString;
  388. static void do_find (TextEditor me) {
  389. if (! theFindString) return; // e.g. when the user does "Find again" before having done any "Find"
  390. integer left, right;
  391. autostring32 text = GuiText_getStringAndSelectionPosition (my textWidget, & left, & right);
  392. char32 *location = str32str (& text [right], theFindString.get());
  393. if (location) {
  394. integer index = location - text.get();
  395. GuiText_setSelection (my textWidget, index, index + str32len (theFindString.get()));
  396. GuiText_scrollToSelection (my textWidget);
  397. #ifdef _WIN32
  398. GuiThing_show (my windowForm);
  399. #endif
  400. } else {
  401. /* Try from the start of the document. */
  402. location = str32str (text.get(), theFindString.get());
  403. if (location) {
  404. integer index = location - text.get();
  405. GuiText_setSelection (my textWidget, index, index + str32len (theFindString.get()));
  406. GuiText_scrollToSelection (my textWidget);
  407. #ifdef _WIN32
  408. GuiThing_show (my windowForm);
  409. #endif
  410. } else {
  411. Melder_beep ();
  412. }
  413. }
  414. }
  415. static void do_replace (TextEditor me) {
  416. if (! theReplaceString) return; // e.g. when the user does "Replace again" before having done any "Replace"
  417. autostring32 selection = GuiText_getSelection (my textWidget);
  418. if (! Melder_equ (selection.get(), theFindString.get())) {
  419. do_find (me);
  420. return;
  421. }
  422. integer left, right;
  423. autostring32 text = GuiText_getStringAndSelectionPosition (my textWidget, & left, & right);
  424. GuiText_replace (my textWidget, left, right, theReplaceString.get());
  425. GuiText_setSelection (my textWidget, left, left + str32len (theReplaceString.get()));
  426. GuiText_scrollToSelection (my textWidget);
  427. #ifdef _WIN32
  428. GuiThing_show (my windowForm);
  429. #endif
  430. }
  431. static void menu_cb_find (TextEditor me, EDITOR_ARGS_FORM) {
  432. EDITOR_FORM (U"Find", nullptr)
  433. TEXTFIELD (findString, U"Find:", U"")
  434. EDITOR_OK
  435. if (theFindString) SET_STRING (findString, theFindString.get());
  436. EDITOR_DO
  437. theFindString = Melder_dup (findString);
  438. do_find (me);
  439. EDITOR_END
  440. }
  441. static void menu_cb_findAgain (TextEditor me, EDITOR_ARGS_DIRECT) {
  442. do_find (me);
  443. }
  444. static void menu_cb_replace (TextEditor me, EDITOR_ARGS_FORM) {
  445. EDITOR_FORM (U"Find", nullptr)
  446. LABEL (U"This is a \"slow\" find-and-replace method;")
  447. LABEL (U"if the selected text is identical to the Find string,")
  448. LABEL (U"the selected text will be replaced by the Replace string;")
  449. LABEL (U"otherwise, the next occurrence of the Find string will be selected.")
  450. LABEL (U"So you typically need two clicks on Apply to get a text replaced.")
  451. TEXTFIELD (findString, U"Find:", U"")
  452. TEXTFIELD (replaceString, U"Replace with:", U"")
  453. EDITOR_OK
  454. if (theFindString) SET_STRING (findString, theFindString.get());
  455. if (theReplaceString) SET_STRING (replaceString, theReplaceString.get());
  456. EDITOR_DO
  457. theFindString = Melder_dup (findString);
  458. theReplaceString = Melder_dup (replaceString);
  459. do_replace (me);
  460. EDITOR_END
  461. }
  462. static void menu_cb_replaceAgain (TextEditor me, EDITOR_ARGS_DIRECT) {
  463. do_replace (me);
  464. }
  465. static void menu_cb_whereAmI (TextEditor me, EDITOR_ARGS_DIRECT) {
  466. integer numberOfLinesLeft, numberOfLinesRight;
  467. if (! getSelectedLines (me, & numberOfLinesLeft, & numberOfLinesRight)) {
  468. Melder_information (U"The cursor is on line ", numberOfLinesLeft, U".");
  469. } else if (numberOfLinesLeft == numberOfLinesRight) {
  470. Melder_information (U"The selection is on line ", numberOfLinesLeft, U".");
  471. } else {
  472. Melder_information (U"The selection runs from line ", numberOfLinesLeft, U" to line ", numberOfLinesRight, U".");
  473. }
  474. }
  475. static void menu_cb_goToLine (TextEditor me, EDITOR_ARGS_FORM) {
  476. EDITOR_FORM (U"Go to line", nullptr)
  477. NATURAL (lineToGo, U"Line", U"1")
  478. EDITOR_OK
  479. integer firstLine, lastLine;
  480. getSelectedLines (me, & firstLine, & lastLine);
  481. SET_INTEGER (lineToGo, firstLine)
  482. EDITOR_DO
  483. autostring32 text = GuiText_getString (my textWidget);
  484. integer currentLine = 1;
  485. int64 left = 0, right = 0;
  486. if (lineToGo == 1) {
  487. for (; text [right] != U'\n' && text [right] != U'\0'; right ++) { }
  488. } else {
  489. for (; text [left] != U'\0'; left ++) {
  490. if (text [left] == U'\n') {
  491. currentLine ++;
  492. if (currentLine == lineToGo) {
  493. left ++;
  494. for (right = left; text [right] != U'\n' && text [right] != U'\0'; right ++) { }
  495. break;
  496. }
  497. }
  498. }
  499. }
  500. if (left == str32len (text.get())) {
  501. right = left;
  502. } else if (text [right] == U'\n') {
  503. right ++;
  504. }
  505. GuiText_setSelection (my textWidget, left, right);
  506. GuiText_scrollToSelection (my textWidget);
  507. EDITOR_END
  508. }
  509. static void menu_cb_convertToCString (TextEditor me, EDITOR_ARGS_DIRECT) {
  510. autostring32 text = GuiText_getString (my textWidget);
  511. char32 buffer [2] = U" ";
  512. const conststring32 hex [16] = { U"0", U"1", U"2", U"3", U"4", U"5", U"6", U"7", U"8", U"9", U"A", U"B", U"C", U"D", U"E", U"F" };
  513. MelderInfo_open ();
  514. MelderInfo_write (U"\"");
  515. for (char32 *p = & text [0]; *p != U'\0'; p ++) {
  516. char32 kar = *p;
  517. if (kar == U'\n') {
  518. MelderInfo_write (U"\\n\"\n\"");
  519. } else if (kar == U'\t') {
  520. MelderInfo_write (U" ");
  521. } else if (kar == U'\"') {
  522. MelderInfo_write (U"\\\"");
  523. } else if (kar == U'\\') {
  524. MelderInfo_write (U"\\\\");
  525. } else if (kar > 127) {
  526. if (kar <= 0x00FFFF) {
  527. MelderInfo_write (U"\\u", hex [kar >> 12], hex [(kar >> 8) & 0x00'000F], hex [(kar >> 4) & 0x00'000F], hex [kar & 0x00'000F]);
  528. } else {
  529. MelderInfo_write (U"\\U", hex [kar >> 28], hex [(kar >> 24) & 0x00'000F], hex [(kar >> 20) & 0x00'000F], hex [(kar >> 16) & 0x00'000F],
  530. hex [(kar >> 12) & 0x00'000F], hex [(kar >> 8) & 0x00'000F], hex [(kar >> 4) & 0x00'000F], hex [kar & 0x00'000F]);
  531. }
  532. } else {
  533. buffer [0] = *p;
  534. MelderInfo_write (& buffer [0]);
  535. }
  536. }
  537. MelderInfo_write (U"\"");
  538. MelderInfo_close ();
  539. }
  540. /***** 'Font' menu *****/
  541. static void updateSizeMenu (TextEditor me) {
  542. if (my fontSizeButton_10) GuiMenuItem_check (my fontSizeButton_10, my p_fontSize == 10);
  543. if (my fontSizeButton_12) GuiMenuItem_check (my fontSizeButton_12, my p_fontSize == 12);
  544. if (my fontSizeButton_14) GuiMenuItem_check (my fontSizeButton_14, my p_fontSize == 14);
  545. if (my fontSizeButton_18) GuiMenuItem_check (my fontSizeButton_18, my p_fontSize == 18);
  546. if (my fontSizeButton_24) GuiMenuItem_check (my fontSizeButton_24, my p_fontSize == 24);
  547. }
  548. static void setFontSize (TextEditor me, int fontSize) {
  549. GuiText_setFontSize (my textWidget, fontSize);
  550. my pref_fontSize () = my p_fontSize = fontSize;
  551. updateSizeMenu (me);
  552. }
  553. static void menu_cb_10 (TextEditor me, EDITOR_ARGS_DIRECT) { setFontSize (me, 10); }
  554. static void menu_cb_12 (TextEditor me, EDITOR_ARGS_DIRECT) { setFontSize (me, 12); }
  555. static void menu_cb_14 (TextEditor me, EDITOR_ARGS_DIRECT) { setFontSize (me, 14); }
  556. static void menu_cb_18 (TextEditor me, EDITOR_ARGS_DIRECT) { setFontSize (me, 18); }
  557. static void menu_cb_24 (TextEditor me, EDITOR_ARGS_DIRECT) { setFontSize (me, 24); }
  558. static void menu_cb_fontSize (TextEditor me, EDITOR_ARGS_FORM) {
  559. EDITOR_FORM (U"Text window: Font size", nullptr)
  560. NATURAL (fontSize, U"Font size (points)", U"12")
  561. EDITOR_OK
  562. SET_INTEGER (fontSize, (integer) my p_fontSize);
  563. EDITOR_DO
  564. setFontSize (me, fontSize);
  565. EDITOR_END
  566. }
  567. static void gui_text_cb_changed (TextEditor me, GuiTextEvent /* event */) {
  568. if (! my dirty) {
  569. my dirty = true;
  570. my v_nameChanged ();
  571. }
  572. }
  573. void structTextEditor :: v_createChildren () {
  574. textWidget = GuiText_createShown (our windowForm, 0, 0, Machine_getMenuBarHeight (), 0, GuiText_SCROLLED);
  575. GuiText_setChangedCallback (textWidget, gui_text_cb_changed, this);
  576. }
  577. void structTextEditor :: v_createMenus () {
  578. TextEditor_Parent :: v_createMenus ();
  579. if (v_fileBased ()) {
  580. Editor_addCommand (this, U"File", U"New", 'N', menu_cb_new);
  581. Editor_addCommand (this, U"File", U"Open...", 'O', menu_cb_open);
  582. Editor_addCommand (this, U"File", U"Reopen from disk", GuiMenu_SHIFT | 'O', menu_cb_reopen);
  583. } else {
  584. Editor_addCommand (this, U"File", U"Clear", 'N', menu_cb_clear);
  585. }
  586. Editor_addCommand (this, U"File", U"-- save --", 0, nullptr);
  587. if (v_fileBased ()) {
  588. Editor_addCommand (this, U"File", U"Save", 'S', menu_cb_save);
  589. Editor_addCommand (this, U"File", U"Save as...", 0, menu_cb_saveAs);
  590. } else {
  591. Editor_addCommand (this, U"File", U"Save as...", 'S', menu_cb_saveAs);
  592. }
  593. Editor_addCommand (this, U"File", U"-- close --", 0, nullptr);
  594. GuiText_setUndoItem (textWidget, Editor_addCommand (this, U"Edit", U"Undo", 'Z', menu_cb_undo));
  595. GuiText_setRedoItem (textWidget, Editor_addCommand (this, U"Edit", U"Redo", 'Y', menu_cb_redo));
  596. Editor_addCommand (this, U"Edit", U"-- cut copy paste --", 0, nullptr);
  597. Editor_addCommand (this, U"Edit", U"Cut", 'X', menu_cb_cut);
  598. Editor_addCommand (this, U"Edit", U"Copy", 'C', menu_cb_copy);
  599. Editor_addCommand (this, U"Edit", U"Paste", 'V', menu_cb_paste);
  600. Editor_addCommand (this, U"Edit", U"Erase", 0, menu_cb_erase);
  601. Editor_addMenu (this, U"Search", 0);
  602. Editor_addCommand (this, U"Search", U"Find...", 'F', menu_cb_find);
  603. Editor_addCommand (this, U"Search", U"Find again", 'G', menu_cb_findAgain);
  604. Editor_addCommand (this, U"Search", U"Replace...", GuiMenu_SHIFT | 'F', menu_cb_replace);
  605. Editor_addCommand (this, U"Search", U"Replace again", GuiMenu_SHIFT | 'G', menu_cb_replaceAgain);
  606. Editor_addCommand (this, U"Search", U"-- line --", 0, nullptr);
  607. Editor_addCommand (this, U"Search", U"Where am I?", 0, menu_cb_whereAmI);
  608. Editor_addCommand (this, U"Search", U"Go to line...", 'L', menu_cb_goToLine);
  609. Editor_addMenu (this, U"Convert", 0);
  610. Editor_addCommand (this, U"Convert", U"Convert to C string", 0, menu_cb_convertToCString);
  611. Editor_addMenu (this, U"Font", 0);
  612. Editor_addCommand (this, U"Font", U"Font size...", 0, menu_cb_fontSize);
  613. fontSizeButton_10 = Editor_addCommand (this, U"Font", U"10", GuiMenu_CHECKBUTTON, menu_cb_10);
  614. fontSizeButton_12 = Editor_addCommand (this, U"Font", U"12", GuiMenu_CHECKBUTTON, menu_cb_12);
  615. fontSizeButton_14 = Editor_addCommand (this, U"Font", U"14", GuiMenu_CHECKBUTTON, menu_cb_14);
  616. fontSizeButton_18 = Editor_addCommand (this, U"Font", U"18", GuiMenu_CHECKBUTTON, menu_cb_18);
  617. fontSizeButton_24 = Editor_addCommand (this, U"Font", U"24", GuiMenu_CHECKBUTTON, menu_cb_24);
  618. }
  619. void TextEditor_init (TextEditor me, conststring32 initialText) {
  620. Editor_init (me, 0, 0, 600, 400, U"", nullptr);
  621. setFontSize (me, my p_fontSize);
  622. if (initialText) {
  623. GuiText_setString (my textWidget, initialText);
  624. my dirty = false; // was set to true in valueChanged callback
  625. Thing_setName (me, U"");
  626. }
  627. theReferencesToAllOpenTextEditors. addItem_ref (me);
  628. }
  629. autoTextEditor TextEditor_create (conststring32 initialText) {
  630. try {
  631. autoTextEditor me = Thing_new (TextEditor);
  632. TextEditor_init (me.get(), initialText);
  633. return me;
  634. } catch (MelderError) {
  635. Melder_throw (U"Text window not created.");
  636. }
  637. }
  638. void TextEditor_showOpen (TextEditor me) {
  639. cb_showOpen (Editor_getMenuCommand (me, U"File", U"Open..."));
  640. }
  641. /* End of file TextEditor.cpp */