GuiText.cpp 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172
  1. /* GuiText.cpp
  2. *
  3. * Copyright (C) 1993-2017 Paul Boersma, 2013 Tom Naughton
  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 "GuiP.h"
  19. #include <locale.h>
  20. Thing_implement (GuiText, GuiControl, 0);
  21. #if motif
  22. #define iam_text \
  23. Melder_assert (widget -> widgetClass == xmTextWidgetClass); \
  24. GuiText me = (GuiText) widget -> userData
  25. #else
  26. #define iam_text \
  27. GuiText me = (GuiText) _GuiObject_getUserData (widget)
  28. #endif
  29. #if motif
  30. static HFONT font10, font12, font14, font18, font24;
  31. /*
  32. * (1) KEYBOARD FOCUS
  33. *
  34. * (1.1) In Motif, the native GUI system handles all that we want:
  35. * every window with text widgets has one text focus widget,
  36. * which will receive global text focus when the window is activated.
  37. * The global text focus is visible to the user.
  38. * The focus changes whenever the user clicks in a text widget that does not have focus.
  39. *
  40. * (1.2) In Windows, the native GUI system handles almost all of the above.
  41. * The exception is that windows have no own text focus widgets;
  42. * there is only a single global text focus. This means that when the user
  43. * clicks on a non-active window, none of the text widgets in this window
  44. * will automatically receive text focus. Yet, the user expects automatic text focus behaviour
  45. * (click a window, then type immediately) in text edit windows (like Praat's script editor)
  46. * and in windows that have a single text widget (like Praat's TextGrid editor).
  47. * For this reason, the WM_COMMAND message handler in Gui.c intercepts the EN_SETFOCUS notification.
  48. * This handler calls _GuiText_handleFocusReception (), which records
  49. * the new text focus widget in its window. When a window is activated,
  50. * we set the global focus explicitly to this window's own text focus widget,
  51. * by calling _GuiText_setTheTextFocus ().
  52. *
  53. * (1.3) On Macintosh, we have to handle all of this explicitly.
  54. *
  55. * (1.4) On Win and Mac, we implement a feature not available in Motif:
  56. * the use of Command-X, Command-C, and Command-V to cut, copy, and paste in text widgets,
  57. * even in dialogs, where there are no menus for which these three commands could be keyboard shortcuts.
  58. * For this reason, _GuiText_handleFocusReception () also stores the global text focus,
  59. * so that the keyboard shortcut handler in Gui.c knows what widget to cut from, copy from, or paste into.
  60. * (It is true that Windows itself stores the global text focus, but this is not always a text widget;
  61. * we want it to always be a text widget, e.g. in the TextGrid editor it is always the text widget,
  62. * never the drawing area, that receives key strokes. In Motif, we will have to program this text
  63. * preference explicitly; see the discussion in FunctionEditor.cpp.)
  64. */
  65. void _GuiText_handleFocusReception (GuiObject widget) {
  66. /*
  67. * On Windows, this is called:
  68. * 1. on a user click in a text widget: WM_COMMAND -> EN_SETFOCUS;
  69. * 2. on window activation: _GuiText_setTheTextFocus () -> SetFocus -> WM_COMMAND -> EN_SETFOCUS;
  70. * 3. on a user click in a push button or toggle button, which would otherwise draw the
  71. * focus away from the text widgets: WM_COMMAND -> _GuiText_setTheTextFocus ().
  72. *
  73. * On Macintosh, this is called:
  74. * 1. on a user click in a text widget: handleControlClick & handleTextEditClick -> _GuiText_setTheTextFocus ();
  75. * 2. on window activation: handleActivateEvent -> _GuiText_setTheTextFocus ().
  76. */
  77. widget -> shell -> textFocus = widget; /* see (1.2) */
  78. theGui.textFocus = widget; /* see (1.4) */
  79. }
  80. void _GuiText_handleFocusLoss (GuiObject widget) {
  81. /*
  82. * me is going out of sight;
  83. * it must stop having global focus.
  84. */
  85. /*
  86. * On Windows, this is called:
  87. * 1. on window deactivation
  88. * 2. on window closure
  89. * 3. on text unmanaging
  90. * 4. on window unmanaging
  91. *
  92. * On Macintosh, this is called:
  93. * 1. on window deactivation
  94. * 2. on window closure
  95. * 3. on text unmanaging
  96. * 4. on window unmanaging
  97. */
  98. if (widget == theGui.textFocus)
  99. theGui.textFocus = nullptr;
  100. }
  101. void _GuiText_setTheTextFocus (GuiObject widget) {
  102. if (! widget || theGui.textFocus == widget
  103. || ! widget -> managed) return; // perhaps not-yet-managed; test: open Praat's DataEditor with a Sound, then type
  104. #if gtk
  105. gtk_widget_grab_focus (GTK_WIDGET (widget)); // not used: gtk is not 1 when motif is 1
  106. #elif motif
  107. SetFocus (widget -> window); // will send an EN_SETFOCUS notification, which will call _GuiText_handleFocusReception ()
  108. #endif
  109. }
  110. /*
  111. * CHANGE NOTIFICATION
  112. */
  113. void _GuiText_handleValueChanged (GuiObject widget) {
  114. iam_text;
  115. if (my d_changedCallback) {
  116. struct structGuiTextEvent event { me };
  117. my d_changedCallback (my d_changedBoss, & event);
  118. }
  119. }
  120. void _GuiText_unmanage (GuiObject widget) {
  121. _GuiText_handleFocusLoss (widget);
  122. _GuiNativeControl_hide (widget);
  123. /*
  124. * The caller will set the unmanage flag to zero, and remanage the parent.
  125. */
  126. }
  127. /*
  128. * VISIBILITY
  129. */
  130. void _GuiWinText_destroy (GuiObject widget) {
  131. if (widget == theGui.textFocus)
  132. theGui.textFocus = nullptr; // remove dangling reference
  133. if (widget == widget -> shell -> textFocus)
  134. widget -> shell -> textFocus = nullptr; // remove dangling reference
  135. iam_text;
  136. DestroyWindow (widget -> window);
  137. forget (me); // NOTE: my widget is not destroyed here
  138. }
  139. void _GuiWinText_map (GuiObject widget) {
  140. iam_text;
  141. ShowWindow (widget -> window, SW_SHOW);
  142. }
  143. static integer NativeText_getLength (GuiObject widget) {
  144. return Edit_GetTextLength (widget -> window); // in UTF-16 code units
  145. }
  146. /*
  147. * SELECTION
  148. */
  149. static bool NativeText_getSelectionRange (GuiObject widget, integer *out_left, integer *out_right) {
  150. Melder_assert (MEMBER (widget, Text));
  151. DWORD left, right;
  152. SendMessage (widget -> window, EM_GETSEL, (WPARAM) & left, (LPARAM) & right);
  153. if (out_left) *out_left = left;
  154. if (out_right) *out_right = right;
  155. return right > left;
  156. }
  157. /*
  158. * PACKAGE
  159. */
  160. void _GuiText_init () {
  161. }
  162. void _GuiText_exit () {
  163. }
  164. #endif
  165. #if gtk || motif
  166. /*
  167. * Undo/Redo history functions
  168. */
  169. static void _GuiText_delete (GuiObject widget, int from_pos, int to_pos) {
  170. #if gtk
  171. if (G_OBJECT_TYPE (G_OBJECT (widget)) == GTK_TYPE_ENTRY) {
  172. gtk_editable_delete_text (GTK_EDITABLE (widget), from_pos, to_pos);
  173. } else {
  174. GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
  175. GtkTextIter from_it, to_it;
  176. gtk_text_buffer_get_iter_at_offset (buffer, & from_it, from_pos);
  177. gtk_text_buffer_get_iter_at_offset (buffer, & to_it, to_pos);
  178. gtk_text_buffer_delete_interactive (buffer, & from_it, & to_it,
  179. gtk_text_view_get_editable (GTK_TEXT_VIEW (widget)));
  180. gtk_text_buffer_place_cursor (buffer, & to_it);
  181. }
  182. #elif motif
  183. #endif
  184. }
  185. static void _GuiText_insert (GuiObject widget, int from_pos, int to_pos, const history_data text) {
  186. #if gtk
  187. if (G_OBJECT_TYPE (G_OBJECT (widget)) == GTK_TYPE_ENTRY) {
  188. gint from_pos_gint = from_pos;
  189. gtk_editable_insert_text (GTK_EDITABLE (widget), text, to_pos - from_pos, & from_pos_gint);
  190. } else {
  191. GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
  192. GtkTextIter it;
  193. gtk_text_buffer_get_iter_at_offset (buffer, & it, from_pos);
  194. gtk_text_buffer_insert_interactive (buffer, & it, text, to_pos - from_pos,
  195. gtk_text_view_get_editable (GTK_TEXT_VIEW (widget)));
  196. gtk_text_buffer_get_iter_at_offset (buffer, & it, to_pos);
  197. gtk_text_buffer_place_cursor (buffer, & it);
  198. }
  199. #elif motif
  200. #endif
  201. }
  202. /* Tests the previous elements of the history for mergability with the one to insert given via parameters.
  203. * If successful, it returns a pointer to the last valid entry in the merged history list, otherwise
  204. * returns nullptr.
  205. * Specifically the function expands the previous insert/delete event(s)
  206. * - with the current, if current also is an insert/delete event and the ranges of previous and current event match
  207. * - with the previous delete and current insert event, in case the ranges of both event-pairs respectively match
  208. */
  209. static history_entry * history_addAndMerge (GuiText me, history_data text_new, integer first, integer last, bool deleted) {
  210. history_entry *he = nullptr;
  211. if (! my d_prev)
  212. return nullptr;
  213. if (my d_prev->type_del == deleted) {
  214. // extend the last history event by this one
  215. if (my d_prev->first == last) {
  216. // most common for backspace key presses
  217. he = my d_prev;
  218. text_new = (char *) realloc (text_new, sizeof (*text_new) * (he->last - first + 1));
  219. memcpy (text_new + last - first, he->text, sizeof (*text_new) * (he->last - he->first + 1));
  220. free (he->text);
  221. he->text = text_new;
  222. he->first = first;
  223. } else if (my d_prev->last == first) {
  224. // most common for ordinary text insertion
  225. he = my d_prev;
  226. he->text = (char *) realloc (he->text, sizeof (*he->text) * (last - he->first + 1));
  227. memcpy (he->text + he->last - he->first, text_new, sizeof (*he->text) * (last - first + 1));
  228. free (text_new);
  229. he->last = last;
  230. } else if (deleted && my d_prev->first == first) {
  231. // most common for delete key presses
  232. he = my d_prev;
  233. he->text = (char *) realloc (he->text, sizeof (*he->text) * (last - first + he->last - he->first + 1));
  234. memcpy (he->text + he->last - he->first, text_new, sizeof (*he->text) * (last - first + 1));
  235. free (text_new);
  236. he->last = last + he->last - he->first;
  237. }
  238. } else {
  239. // prev->type_del != deleted, no simple expansion possible, check for double expansion
  240. if (! deleted && my d_prev->prev && my d_prev->prev->prev) {
  241. history_entry *del_one = my d_prev;
  242. history_entry *ins_mult = del_one->prev;
  243. history_entry *del_mult = ins_mult->prev;
  244. integer from1 = del_mult->first, to1 = del_mult->last;
  245. integer from2 = ins_mult->first, to2 = ins_mult->last;
  246. integer from3 = del_one->first, to3 = del_one->last;
  247. if (from3 == first && to3 == last && from2 == from1 && to2 == to1 && to1 == first &&
  248. ! ins_mult->type_del && del_mult->type_del) {
  249. // most common for overwriting text
  250. /* So the layout is as follows:
  251. *
  252. * del_mult ins_mult del_one current (parameters)
  253. * [del, from1, to1, "uvw"] [ins, from1, to1, "abc"] [del, to1, to3, "x"] [ins, to1, to3, "d"]
  254. * n >= 1 characters n characters 1 character 1 character
  255. *
  256. * So merge those four events into two events by expanding del_mult by del_one and ins_mult by current */
  257. del_mult->text = (char *) realloc (del_mult->text, sizeof (*del_mult->text) * (to3 - from1 + 1));
  258. ins_mult->text = (char *) realloc (ins_mult->text, sizeof (*ins_mult->text) * (to3 - from1 + 1));
  259. memcpy (del_mult->text + to1 - from1, del_one->text, sizeof (*del_mult->text) * (to3 - to1 + 1));
  260. memcpy (ins_mult->text + to1 - from1, text_new , sizeof (*del_mult->text) * (to3 - to1 + 1));
  261. del_mult->last = to3;
  262. ins_mult->last = to3;
  263. free (del_one->text);
  264. free (del_one);
  265. free (text_new);
  266. my d_prev = he = ins_mult;
  267. }
  268. }
  269. }
  270. return he;
  271. }
  272. /* Inserts a new history action, thereby removing any remaining 'redo' steps;
  273. * text_new a newly allocated string that will be freed by a history function
  274. * (history_add or history_clear)
  275. */
  276. static void history_add (GuiText me, history_data text_new, integer first, integer last, bool deleted) {
  277. // delete all newer entries; from here on there is no 'Redo' until the next 'Undo' is performed
  278. history_entry *old_hnext = my d_next, *hnext;
  279. while (old_hnext) {
  280. hnext = old_hnext->next;
  281. free (old_hnext->text);
  282. free (old_hnext);
  283. old_hnext = hnext;
  284. }
  285. my d_next = nullptr;
  286. history_entry *he = history_addAndMerge (me, text_new, first, last, deleted);
  287. if (! he) {
  288. he = (history_entry *) malloc (sizeof *he);
  289. he->first = first;
  290. he->last = last;
  291. he->type_del = deleted;
  292. he->text = text_new;
  293. he->prev = my d_prev;
  294. he->next = nullptr;
  295. if (my d_prev)
  296. my d_prev->next = he;
  297. }
  298. my d_prev = he;
  299. he->next = nullptr;
  300. if (my d_undo_item) GuiThing_setSensitive (my d_undo_item, true);
  301. if (my d_redo_item) GuiThing_setSensitive (my d_redo_item, false);
  302. }
  303. static bool history_has_undo (GuiText me) {
  304. return !! my d_prev;
  305. }
  306. static bool history_has_redo (GuiText me) {
  307. return !! my d_next;
  308. }
  309. static void history_do (GuiText me, bool undo) {
  310. history_entry *he = undo ? my d_prev : my d_next;
  311. if (! he) // TODO: this function should not be called in that case
  312. return;
  313. my d_history_change = 1;
  314. if (undo ^ he->type_del) {
  315. _GuiText_delete (my d_widget, he->first, he->last);
  316. } else {
  317. _GuiText_insert (my d_widget, he->first, he->last, he->text);
  318. }
  319. my d_history_change = 0;
  320. if (undo) {
  321. my d_next = my d_prev;
  322. my d_prev = my d_prev->prev;
  323. } else {
  324. my d_prev = my d_next;
  325. my d_next = my d_next->next;
  326. }
  327. if (my d_undo_item) GuiThing_setSensitive (my d_undo_item, history_has_undo (me));
  328. if (my d_redo_item) GuiThing_setSensitive (my d_redo_item, history_has_redo (me));
  329. }
  330. static void history_clear (GuiText me) {
  331. history_entry *h1, *h2;
  332. h1 = my d_prev;
  333. while (h1) {
  334. h2 = h1->prev;
  335. free (h1->text);
  336. free (h1);
  337. h1 = h2;
  338. }
  339. my d_prev = nullptr;
  340. h1 = my d_next;
  341. while (h1) {
  342. h2 = h1->next;
  343. free (h1->text);
  344. free (h1);
  345. h1 = h2;
  346. }
  347. my d_next = nullptr;
  348. if (my d_undo_item) GuiThing_setSensitive (my d_undo_item, false);
  349. if (my d_redo_item) GuiThing_setSensitive (my d_redo_item, false);
  350. }
  351. #endif
  352. /*
  353. * CALLBACKS
  354. */
  355. #if gtk
  356. static void _GuiGtkEntry_history_delete_cb (GtkEditable *ed, gint from, gint to, gpointer void_me) {
  357. iam (GuiText);
  358. trace (U"begin");
  359. if (my d_history_change) return;
  360. history_add (me, gtk_editable_get_chars (GTK_EDITABLE (ed), from, to), from, to, 1);
  361. }
  362. static void _GuiGtkEntry_history_insert_cb (GtkEditable *ed, gchar *utf8_text, gint len, gint *from, gpointer void_me) {
  363. (void) ed;
  364. iam (GuiText);
  365. trace (U"begin");
  366. if (my d_history_change) return;
  367. gchar *text = (gchar *) malloc (sizeof (gchar) * (len + 1));
  368. strcpy (text, utf8_text);
  369. history_add (me, text, *from, *from + len, 0);
  370. }
  371. static void _GuiGtkTextBuf_history_delete_cb (GtkTextBuffer *buffer, GtkTextIter *from, GtkTextIter *to, gpointer void_me) {
  372. iam (GuiText);
  373. trace (U"begin");
  374. if (my d_history_change) return;
  375. int from_pos = gtk_text_iter_get_offset (from);
  376. int to_pos = gtk_text_iter_get_offset (to);
  377. history_add (me, gtk_text_buffer_get_text (buffer, from, to, false), from_pos, to_pos, 1);
  378. }
  379. static void _GuiGtkTextBuf_history_insert_cb (GtkTextBuffer *buffer, GtkTextIter *from, gchar *utf8_text, gint len, gpointer void_me) {
  380. (void) buffer;
  381. iam (GuiText);
  382. trace (U"begin");
  383. if (my d_history_change) return;
  384. int from_pos = gtk_text_iter_get_offset (from);
  385. gchar *text = (gchar *) malloc (sizeof (gchar) * (len + 1));
  386. strcpy (text, utf8_text);
  387. history_add (me, text, from_pos, from_pos + len, 0);
  388. }
  389. static void _GuiGtkText_valueChangedCallback (GuiObject widget, gpointer void_me) {
  390. iam (GuiText);
  391. trace (U"begin");
  392. Melder_assert (me);
  393. if (my d_changedCallback) {
  394. struct structGuiTextEvent event { me };
  395. my d_changedCallback (my d_changedBoss, & event);
  396. }
  397. }
  398. static void _GuiGtkText_destroyCallback (GuiObject widget, gpointer void_me) {
  399. (void) widget;
  400. iam (GuiText);
  401. Melder_assert (me);
  402. Melder_assert (my classInfo == classGuiText);
  403. trace (U"begin");
  404. if (my d_undo_item) {
  405. trace (U"undo");
  406. //g_object_unref (my d_undo_item -> d_widget);
  407. }
  408. if (my d_redo_item) {
  409. trace (U"redo");
  410. //g_object_unref (my d_redo_item -> d_widget);
  411. }
  412. my d_undo_item = nullptr;
  413. my d_redo_item = nullptr;
  414. trace (U"history");
  415. history_clear (me);
  416. forget (me);
  417. }
  418. #elif motif
  419. #elif cocoa
  420. @implementation GuiCocoaTextField {
  421. GuiText d_userData;
  422. }
  423. - (void) dealloc { // override
  424. GuiText me = d_userData;
  425. forget (me);
  426. trace (U"deleting a text field");
  427. [super dealloc];
  428. }
  429. - (GuiThing) getUserData {
  430. return d_userData;
  431. }
  432. - (void) setUserData: (GuiThing) userData {
  433. Melder_assert (userData == nullptr || Thing_isa (userData, classGuiText));
  434. d_userData = static_cast <GuiText> (userData);
  435. }
  436. - (void) textDidChange: (NSNotification *) notification {
  437. (void) notification;
  438. GuiText me = d_userData;
  439. if (me && my d_changedCallback) {
  440. struct structGuiTextEvent event { me };
  441. my d_changedCallback (my d_changedBoss, & event);
  442. }
  443. }
  444. @end
  445. @implementation GuiCocoaTextView {
  446. GuiText d_userData;
  447. }
  448. - (void) dealloc { // override
  449. GuiText me = d_userData;
  450. forget (me);
  451. trace (U"deleting a text view");
  452. [super dealloc];
  453. }
  454. - (GuiThing) getUserData {
  455. return d_userData;
  456. }
  457. - (void) setUserData: (GuiThing) userData {
  458. Melder_assert (userData == nullptr || Thing_isa (userData, classGuiText));
  459. d_userData = static_cast <GuiText> (userData);
  460. }
  461. /*
  462. * The NSTextViewDelegate protocol.
  463. * While NSTextDelegate simply has textDidChange:, that method doesn't seem to respond when the text is changed programmatically.
  464. */
  465. // - (void) textDidChange: (NSNotification *) notification {
  466. - (BOOL) textView: (NSTextView *) aTextView shouldChangeTextInRange: (NSRange) affectedCharRange replacementString: (NSString *) replacementString {
  467. (void) aTextView;
  468. (void) affectedCharRange;
  469. (void) replacementString;
  470. trace (U"changing text to: ", Melder_peek8to32 ([replacementString UTF8String]));
  471. GuiText me = d_userData;
  472. if (me && my d_changedCallback) {
  473. struct structGuiTextEvent event { me };
  474. my d_changedCallback (my d_changedBoss, & event);
  475. }
  476. return YES;
  477. }
  478. @end
  479. #endif
  480. GuiText GuiText_create (GuiForm parent, int left, int right, int top, int bottom, uint32 flags) {
  481. autoGuiText me = Thing_new (GuiText);
  482. my d_shell = parent -> d_shell;
  483. my d_parent = parent;
  484. #if gtk
  485. trace (U"before creating a GTK text widget: locale is ", Melder_peek8to32 (setlocale (LC_ALL, nullptr)));
  486. if (flags & GuiText_SCROLLED) {
  487. GtkWrapMode ww;
  488. GuiObject scrolled = gtk_scrolled_window_new (nullptr, nullptr);
  489. gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  490. my d_widget = gtk_text_view_new ();
  491. gtk_container_add (GTK_CONTAINER (scrolled), GTK_WIDGET (my d_widget));
  492. gtk_widget_show (GTK_WIDGET (scrolled));
  493. gtk_text_view_set_editable (GTK_TEXT_VIEW (my d_widget), (flags & GuiText_NONEDITABLE) == 0);
  494. if ((flags & GuiText_WORDWRAP) != 0)
  495. ww = GTK_WRAP_WORD_CHAR;
  496. else
  497. ww = GTK_WRAP_NONE;
  498. gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (my d_widget), ww);
  499. GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (my d_widget));
  500. g_signal_connect (G_OBJECT (buffer), "delete-range", G_CALLBACK (_GuiGtkTextBuf_history_delete_cb), me.get());
  501. g_signal_connect (G_OBJECT (buffer), "insert-text", G_CALLBACK (_GuiGtkTextBuf_history_insert_cb), me.get());
  502. g_signal_connect (G_OBJECT (buffer), "changed", G_CALLBACK (_GuiGtkText_valueChangedCallback), me.get());
  503. _GuiObject_setUserData (my d_widget, me.get());
  504. _GuiObject_setUserData (scrolled, me.get());
  505. my v_positionInForm (scrolled, left, right, top, bottom, parent);
  506. } else {
  507. my d_widget = gtk_entry_new ();
  508. gtk_editable_set_editable (GTK_EDITABLE (my d_widget), (flags & GuiText_NONEDITABLE) == 0);
  509. g_signal_connect (G_OBJECT (my d_widget), "delete-text", G_CALLBACK (_GuiGtkEntry_history_delete_cb), me.get());
  510. g_signal_connect (G_OBJECT (my d_widget), "insert-text", G_CALLBACK (_GuiGtkEntry_history_insert_cb), me.get());
  511. g_signal_connect (GTK_EDITABLE (my d_widget), "changed", G_CALLBACK (_GuiGtkText_valueChangedCallback), me.get());
  512. //GTK_WIDGET_UNSET_FLAGS (my d_widget, GTK_CAN_DEFAULT);
  513. _GuiObject_setUserData (my d_widget, me.get());
  514. my v_positionInForm (my d_widget, left, right, top, bottom, parent);
  515. gtk_entry_set_activates_default (GTK_ENTRY (my d_widget), true);
  516. }
  517. trace (U"after creating a GTK text widget: locale is ", Melder_peek8to32 (setlocale (LC_ALL, nullptr)));
  518. my d_prev = nullptr;
  519. my d_next = nullptr;
  520. my d_history_change = 0;
  521. my d_undo_item = nullptr;
  522. my d_redo_item = nullptr;
  523. g_signal_connect (G_OBJECT (my d_widget), "destroy", G_CALLBACK (_GuiGtkText_destroyCallback), me.get());
  524. #elif motif
  525. my d_widget = _Gui_initializeWidget (xmTextWidgetClass, parent -> d_widget, flags & GuiText_SCROLLED ? U"scrolledText" : U"text");
  526. _GuiObject_setUserData (my d_widget, me.get());
  527. my d_editable = (flags & GuiText_NONEDITABLE) == 0;
  528. my d_widget -> window = CreateWindow (L"edit", nullptr, WS_CHILD | WS_BORDER
  529. | ( flags & GuiText_WORDWRAP ? ES_AUTOVSCROLL : ES_AUTOHSCROLL )
  530. | ES_MULTILINE | WS_CLIPSIBLINGS
  531. | ( flags & GuiText_SCROLLED ? WS_HSCROLL | WS_VSCROLL : 0 ),
  532. my d_widget -> x, my d_widget -> y, my d_widget -> width, my d_widget -> height,
  533. my d_widget -> parent -> window, (HMENU) 1, theGui.instance, nullptr);
  534. SetWindowLongPtr (my d_widget -> window, GWLP_USERDATA, (LONG_PTR) my d_widget);
  535. if (! font10) {
  536. font10 = CreateFont (13, 0, 0, 0, 0, 0, 0, 0, DEFAULT_CHARSET, 0, 0, 0, 0/*FIXED_PITCH | FF_MODERN*/, /*L"Doulos SIL"*/L"Courier New");
  537. font12 = CreateFont (16, 0, 0, 0, 0, 0, 0, 0, DEFAULT_CHARSET, 0, 0, 0, 0/*FIXED_PITCH | FF_MODERN*/, /*L"Doulos SIL"*/L"Courier New");
  538. font14 = CreateFont (19, 0, 0, 0, 0, 0, 0, 0, DEFAULT_CHARSET, 0, 0, 0, 0/*FIXED_PITCH | FF_MODERN*/, /*L"Doulos SIL"*/L"Courier New");
  539. font18 = CreateFont (24, 0, 0, 0, 0, 0, 0, 0, DEFAULT_CHARSET, 0, 0, 0, 0/*FIXED_PITCH | FF_MODERN*/, /*L"Doulos SIL"*/L"Courier New");
  540. font24 = CreateFont (32, 0, 0, 0, 0, 0, 0, 0, DEFAULT_CHARSET, 0, 0, 0, 0/*FIXED_PITCH | FF_MODERN*/, /*L"Doulos SIL"*/L"Courier New");
  541. }
  542. SetWindowFont (my d_widget -> window, font12 /*theScrolledHint ? font : GetStockFont (ANSI_VAR_FONT)*/, false);
  543. Edit_LimitText (my d_widget -> window, 0);
  544. my v_positionInForm (my d_widget, left, right, top, bottom, parent);
  545. /*
  546. * The first created text widget shall attract the input focus.
  547. */
  548. if (! my d_widget -> shell -> textFocus) {
  549. my d_widget -> shell -> textFocus = my d_widget; // even if not-yet-managed. But in that case it will not receive global focus
  550. }
  551. #elif cocoa
  552. if (flags & GuiText_SCROLLED) {
  553. my d_cocoaScrollView = [[GuiCocoaScrolledWindow alloc] init];
  554. [my d_cocoaScrollView setUserData: nullptr]; // because those user data can only be GuiScrolledWindow
  555. my d_widget = my d_cocoaScrollView;
  556. my v_positionInForm (my d_widget, left, right, top, bottom, parent);
  557. [my d_cocoaScrollView setBorderType: NSNoBorder];
  558. [my d_cocoaScrollView setHasHorizontalScroller: YES];
  559. [my d_cocoaScrollView setHasVerticalScroller: YES];
  560. [my d_cocoaScrollView setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
  561. NSSize contentSize = [my d_cocoaScrollView contentSize];
  562. my d_cocoaTextView = [[GuiCocoaTextView alloc] initWithFrame: NSMakeRect (0, 0, contentSize. width, contentSize. height)];
  563. [my d_cocoaTextView setUserData: me.get()];
  564. if (Melder_systemVersion < 101100) {
  565. [my d_cocoaTextView setMinSize: NSMakeSize (0.0, contentSize.height)];
  566. } else {
  567. [my d_cocoaTextView setMinSize: NSMakeSize (contentSize. width, contentSize.height)]; // El Capitan Developer Beta 2
  568. }
  569. [my d_cocoaTextView setMaxSize: NSMakeSize (FLT_MAX, FLT_MAX)];
  570. [my d_cocoaTextView setVerticallyResizable: YES];
  571. [my d_cocoaTextView setHorizontallyResizable: YES];
  572. [my d_cocoaTextView setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
  573. [[my d_cocoaTextView textContainer] setContainerSize: NSMakeSize (FLT_MAX, FLT_MAX)];
  574. [[my d_cocoaTextView textContainer] setWidthTracksTextView: NO];
  575. [my d_cocoaScrollView setDocumentView: my d_cocoaTextView]; // the scroll view will own the text view?
  576. [my d_cocoaTextView release]; // so we release the text view itself
  577. [[my d_cocoaScrollView window] makeFirstResponder: my d_cocoaTextView];
  578. static NSFont *theTextFont;
  579. if (! theTextFont) {
  580. theTextFont = [[NSFont systemFontOfSize: 13.0] retain];
  581. theTextFont = [[NSFont fontWithName: @"Menlo" size: 12.0] retain];
  582. }
  583. [my d_cocoaTextView setFont: theTextFont];
  584. [my d_cocoaTextView setAllowsUndo: YES];
  585. [my d_cocoaTextView turnOffLigatures: nil];
  586. [my d_cocoaTextView setSmartInsertDeleteEnabled: NO];
  587. [my d_cocoaTextView setAutomaticQuoteSubstitutionEnabled: NO];
  588. [my d_cocoaTextView setAutomaticTextReplacementEnabled: NO];
  589. [my d_cocoaTextView setAutomaticDashSubstitutionEnabled: NO];
  590. [my d_cocoaTextView setDelegate: my d_cocoaTextView];
  591. } else {
  592. my d_widget = [[GuiCocoaTextField alloc] init];
  593. my v_positionInForm (my d_widget, left, right, top, bottom, parent);
  594. [(GuiCocoaTextField *) my d_widget setUserData: me.get()];
  595. [(NSTextField *) my d_widget setEditable: YES];
  596. static NSFont *theTextFont;
  597. if (! theTextFont) {
  598. theTextFont = [[NSFont systemFontOfSize: 13.0] retain];
  599. }
  600. [(NSTextField *) my d_widget setFont: theTextFont];
  601. }
  602. #endif
  603. return me.releaseToAmbiguousOwner();
  604. }
  605. GuiText GuiText_createShown (GuiForm parent, int left, int right, int top, int bottom, uint32 flags) {
  606. GuiText me = GuiText_create (parent, left, right, top, bottom, flags);
  607. GuiThing_show (me);
  608. return me;
  609. }
  610. void GuiText_copy (GuiText me) {
  611. #if gtk
  612. if (G_OBJECT_TYPE (G_OBJECT (my d_widget)) == GTK_TYPE_ENTRY) {
  613. gtk_editable_copy_clipboard (GTK_EDITABLE (my d_widget));
  614. } else if (G_OBJECT_TYPE (G_OBJECT (my d_widget)) == GTK_TYPE_TEXT_VIEW) {
  615. GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (my d_widget));
  616. GtkClipboard *cb = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
  617. gtk_text_buffer_copy_clipboard (buffer, cb);
  618. }
  619. #elif motif
  620. if (! NativeText_getSelectionRange (my d_widget, nullptr, nullptr)) return;
  621. SendMessage (my d_widget -> window, WM_COPY, 0, 0);
  622. #elif cocoa
  623. if (my d_cocoaTextView) {
  624. [my d_cocoaTextView copy: nil];
  625. } else {
  626. [[[(GuiCocoaTextField *) my d_widget window] fieldEditor: NO forObject: nil] copy: nil];
  627. }
  628. #endif
  629. }
  630. void GuiText_cut (GuiText me) {
  631. #if gtk
  632. if (G_OBJECT_TYPE (G_OBJECT (my d_widget)) == GTK_TYPE_ENTRY) {
  633. gtk_editable_cut_clipboard (GTK_EDITABLE (my d_widget));
  634. } else if (G_OBJECT_TYPE (G_OBJECT (my d_widget)) == GTK_TYPE_TEXT_VIEW) {
  635. GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (my d_widget));
  636. GtkClipboard *cb = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
  637. gtk_text_buffer_cut_clipboard (buffer, cb, gtk_text_view_get_editable (GTK_TEXT_VIEW (my d_widget)));
  638. }
  639. #elif motif
  640. if (! my d_editable || ! NativeText_getSelectionRange (my d_widget, nullptr, nullptr)) return;
  641. SendMessage (my d_widget -> window, WM_CUT, 0, 0); // this will send the EN_CHANGE message, hence no need to call the valueChangedCallbacks
  642. UpdateWindow (my d_widget -> window);
  643. #elif cocoa
  644. if (my d_cocoaTextView) {
  645. [my d_cocoaTextView cut: nil];
  646. } else {
  647. [[[(GuiCocoaTextField *) my d_widget window] fieldEditor: NO forObject: nil] cut: nil];
  648. }
  649. #endif
  650. }
  651. autostring32 GuiText_getSelection (GuiText me) {
  652. #if gtk
  653. // first = gtk_text_iter_get_offset (& start);
  654. // last = gtk_text_iter_get_offset (& end);
  655. if (G_OBJECT_TYPE (G_OBJECT (my d_widget)) == GTK_TYPE_ENTRY) {
  656. gint start, end;
  657. gtk_editable_get_selection_bounds (GTK_EDITABLE (my d_widget), & start, & end);
  658. if (end > start) { // at least one character selected?
  659. gchar *text = gtk_editable_get_chars (GTK_EDITABLE (my d_widget), start, end);
  660. autostring32 result = Melder_8to32 (text);
  661. g_free (text);
  662. return result;
  663. }
  664. } else if (G_OBJECT_TYPE (G_OBJECT (my d_widget)) == GTK_TYPE_TEXT_VIEW) {
  665. GtkTextBuffer *textBuffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (my d_widget));
  666. if (gtk_text_buffer_get_has_selection (textBuffer)) { // at least one character selected?
  667. GtkTextIter start, end;
  668. gtk_text_buffer_get_selection_bounds (textBuffer, & start, & end);
  669. gchar *text = gtk_text_buffer_get_text (textBuffer, & start, & end, true);
  670. autostring32 result = Melder_8to32 (text);
  671. g_free (text);
  672. return result;
  673. }
  674. }
  675. #elif motif
  676. integer startW, endW;
  677. (void) NativeText_getSelectionRange (my d_widget, & startW, & endW);
  678. if (endW > startW) { // at least one character selected?
  679. /*
  680. Get all text.
  681. */
  682. integer lengthW = NativeText_getLength (my d_widget); // in UTF-16 code units
  683. WCHAR *bufferW = Melder_malloc_f (WCHAR, lengthW + 1);
  684. GetWindowTextW (my d_widget -> window, bufferW, lengthW + 1);
  685. /*
  686. Zoom in on selection.
  687. */
  688. lengthW = endW - startW;
  689. memmove (bufferW, bufferW + startW, lengthW * sizeof (WCHAR)); // not because of realloc, but because of free!
  690. bufferW [lengthW] = U'\0';
  691. autostring32 result = Melder_dup_f (Melder_peekWto32 (bufferW));
  692. (void) Melder_killReturns_inplace (result.get()); // AFTER zooming!
  693. return result;
  694. }
  695. #elif cocoa
  696. integer start, end;
  697. autostring32 selection = GuiText_getStringAndSelectionPosition (me, & start, & end);
  698. integer length = end - start;
  699. if (length > 0) {
  700. autostring32 result (length, true);
  701. memcpy (result.get(), & selection [start], integer_to_uinteger (length) * sizeof (char32));
  702. result [length] = U'\0';
  703. (void) Melder_killReturns_inplace (result.get());
  704. return result;
  705. }
  706. #endif
  707. return autostring32(); // zero characters selected
  708. }
  709. autostring32 GuiText_getString (GuiText me) {
  710. integer first, last;
  711. return GuiText_getStringAndSelectionPosition (me, & first, & last);
  712. }
  713. autostring32 GuiText_getStringAndSelectionPosition (GuiText me, integer *first, integer *last) {
  714. #if gtk
  715. if (G_OBJECT_TYPE (G_OBJECT (my d_widget)) == GTK_TYPE_ENTRY) {
  716. gint first_gint, last_gint;
  717. gtk_editable_get_selection_bounds (GTK_EDITABLE (my d_widget), & first_gint, & last_gint); // expressed in Unicode code points!
  718. *first = first_gint;
  719. *last = last_gint;
  720. return Melder_8to32 (gtk_entry_get_text (GTK_ENTRY (my d_widget)));
  721. } else if (G_OBJECT_TYPE (G_OBJECT (my d_widget)) == GTK_TYPE_TEXT_VIEW) {
  722. GtkTextBuffer *textBuffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (my d_widget));
  723. GtkTextIter start, end;
  724. gtk_text_buffer_get_start_iter (textBuffer, & start);
  725. gtk_text_buffer_get_end_iter (textBuffer, & end);
  726. gchar *text = gtk_text_buffer_get_text (textBuffer, & start, & end, true); // TODO: Hidden chars ook maar doen he?
  727. autostring32 result = Melder_8to32 (text);
  728. g_free (text);
  729. gtk_text_buffer_get_selection_bounds (textBuffer, & start, & end);
  730. *first = gtk_text_iter_get_offset (& start);
  731. *last = gtk_text_iter_get_offset (& end);
  732. return result;
  733. }
  734. return autostring32();
  735. #elif motif
  736. integer lengthW = NativeText_getLength (my d_widget);
  737. autostringW bufferW (lengthW , true);
  738. GetWindowTextW (my d_widget -> window, bufferW.get(), lengthW + 1);
  739. integer firstW, lastW;
  740. (void) NativeText_getSelectionRange (my d_widget, & firstW, & lastW);
  741. integer differenceFirst = 0;
  742. for (integer i = 0; i < firstW; i ++) {
  743. if (bufferW [i] == 13 && (bufferW [i + 1] == L'\n' || bufferW [i + 1] == 0x0085))
  744. differenceFirst ++;
  745. if (bufferW [i] >= 0xDC00 && bufferW [i] <= 0xDFFF)
  746. differenceFirst ++;
  747. }
  748. *first = firstW - differenceFirst;
  749. integer differenceLast = differenceFirst;
  750. for (integer i = firstW; i < lastW; i ++) {
  751. if (bufferW [i] == 13 && (bufferW [i + 1] == L'\n' || bufferW [i + 1] == 0x0085))
  752. differenceLast ++;
  753. if (bufferW [i] >= 0xDC00 && bufferW [i] <= 0xDFFF)
  754. differenceLast ++;
  755. }
  756. *last = lastW - differenceLast;
  757. autostring32 result = Melder_dup_f (Melder_peekWto32 (bufferW.get()));
  758. (void) Melder_killReturns_inplace (result.get());
  759. return result;
  760. #elif cocoa
  761. if (my d_cocoaTextView) {
  762. NSString *nsString = [my d_cocoaTextView string];
  763. autostring32 result = Melder_8to32 ([nsString UTF8String]);
  764. trace (U"string ", result.get());
  765. NSRange nsRange = [my d_cocoaTextView selectedRange];
  766. *first = uinteger_to_integer (nsRange. location);
  767. *last = *first + uinteger_to_integer (nsRange. length);
  768. for (integer i = 0; i < *first; i ++) {
  769. if (result [i] > 0xFFFF) {
  770. (*first) --;
  771. (*last) --;
  772. }
  773. }
  774. for (integer i = *first; i < *last; i ++) {
  775. if (result [i] > 0xFFFF)
  776. (*last) --;
  777. }
  778. return result;
  779. } else {
  780. NSString *nsString = [(NSTextField *) my d_widget stringValue];
  781. autostring32 result = Melder_8to32 ([nsString UTF8String]);
  782. trace (U"string ", result.get());
  783. NSRange nsRange = [[[(NSTextField *) my d_widget window] fieldEditor: NO forObject: nil] selectedRange];
  784. *first = uinteger_to_integer (nsRange. location);
  785. *last = *first + uinteger_to_integer (nsRange. length);
  786. for (integer i = 0; i < *first; i ++) {
  787. if (result [i] > 0xFFFF) {
  788. (*first) --;
  789. (*last) --;
  790. }
  791. }
  792. for (integer i = *first; i < *last; i ++) {
  793. if (result [i] > 0xFFFF)
  794. (*last) --;
  795. }
  796. return result;
  797. }
  798. #else
  799. return autostring32();
  800. #endif
  801. }
  802. void GuiText_paste (GuiText me) {
  803. #if gtk
  804. if (G_OBJECT_TYPE (G_OBJECT (my d_widget)) == GTK_TYPE_ENTRY) {
  805. gtk_editable_paste_clipboard (GTK_EDITABLE (my d_widget));
  806. } else if (G_OBJECT_TYPE (G_OBJECT (my d_widget)) == GTK_TYPE_TEXT_VIEW) {
  807. GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (my d_widget));
  808. GtkClipboard *cb = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
  809. gtk_text_buffer_paste_clipboard (buffer, cb, nullptr, gtk_text_view_get_editable (GTK_TEXT_VIEW (my d_widget)));
  810. }
  811. #elif motif
  812. if (! my d_editable) return;
  813. SendMessage (my d_widget -> window, WM_PASTE, 0, 0); // this will send the EN_CHANGE message, hence no need to call the valueChangedCallbacks
  814. UpdateWindow (my d_widget -> window);
  815. #elif cocoa
  816. if (my d_cocoaTextView) {
  817. [my d_cocoaTextView pasteAsPlainText: nil];
  818. } else {
  819. [[[(GuiCocoaTextField *) my d_widget window] fieldEditor: NO forObject: nil] pasteAsPlainText: nil];
  820. }
  821. #endif
  822. }
  823. void GuiText_redo (GuiText me) {
  824. #if gtk || motif
  825. history_do (me, 0);
  826. #elif cocoa
  827. if (my d_cocoaTextView) {
  828. [[my d_cocoaTextView undoManager] redo];
  829. }
  830. #endif
  831. }
  832. void GuiText_remove (GuiText me) {
  833. #if gtk
  834. if (G_OBJECT_TYPE (G_OBJECT (my d_widget)) == GTK_TYPE_ENTRY) {
  835. gtk_editable_delete_selection (GTK_EDITABLE (my d_widget));
  836. } else if (G_OBJECT_TYPE (G_OBJECT (my d_widget)) == GTK_TYPE_TEXT_VIEW) {
  837. GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (my d_widget));
  838. gtk_text_buffer_delete_selection (buffer, true, gtk_text_view_get_editable (GTK_TEXT_VIEW (my d_widget)));
  839. }
  840. #elif motif
  841. if (! my d_editable || ! NativeText_getSelectionRange (my d_widget, nullptr, nullptr)) return;
  842. SendMessage (my d_widget -> window, WM_CLEAR, 0, 0); // this will send the EN_CHANGE message, hence no need to call the valueChangedCallbacks
  843. UpdateWindow (my d_widget -> window);
  844. #elif cocoa
  845. if (my d_cocoaTextView) {
  846. [my d_cocoaTextView delete: nil];
  847. }
  848. #endif
  849. }
  850. void GuiText_replace (GuiText me, integer from_pos, integer to_pos, conststring32 text) {
  851. #if gtk
  852. const gchar *newText = Melder_peek32to8 (text);
  853. if (G_OBJECT_TYPE (G_OBJECT (my d_widget)) == GTK_TYPE_ENTRY) {
  854. gtk_editable_delete_text (GTK_EDITABLE (my d_widget), from_pos, to_pos);
  855. gint from_pos_gint = from_pos;
  856. gtk_editable_insert_text (GTK_EDITABLE (my d_widget), newText, g_utf8_strlen (newText, -1), & from_pos_gint);
  857. } else if (G_OBJECT_TYPE (G_OBJECT (my d_widget)) == GTK_TYPE_TEXT_VIEW) {
  858. GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (my d_widget));
  859. GtkTextIter from_it, to_it;
  860. gtk_text_buffer_get_iter_at_offset (buffer, & from_it, from_pos);
  861. gtk_text_buffer_get_iter_at_offset (buffer, & to_it, to_pos);
  862. gtk_text_buffer_delete_interactive (buffer, & from_it, & to_it,
  863. gtk_text_view_get_editable (GTK_TEXT_VIEW (my d_widget)));
  864. gtk_text_buffer_insert_interactive (buffer, & from_it, newText, g_utf8_strlen (newText, -1),
  865. gtk_text_view_get_editable (GTK_TEXT_VIEW (my d_widget)));
  866. }
  867. #elif motif
  868. Melder_assert (MEMBER (my d_widget, Text));
  869. autostring32 winText (2 * str32len (text), true); // all newlines
  870. char32 *to = & winText [0];
  871. /*
  872. Replace all LF with CR/LF.
  873. */
  874. for (const char32 *from = & text [0]; *from != U'\0'; from ++, to ++)
  875. if (*from == U'\n') { *to = 13; * ++ to = U'\n'; } else *to = *from;
  876. *to = U'\0';
  877. /*
  878. We DON'T replace any text without selecting it, so we can deselect any other text,
  879. thus allowing ourselves to select [from_pos, to_pos] and use the REPLACESEL message.
  880. */
  881. GuiText_setSelection (me, from_pos, to_pos);
  882. Edit_ReplaceSel (my d_widget -> window, Melder_peek32toW (winText.get()));
  883. UpdateWindow (my d_widget -> window);
  884. #elif cocoa
  885. if (my d_cocoaTextView) {
  886. integer numberOfLeadingHighUnicodeValues = 0, numberOfSelectedHighUnicodeValues = 0;
  887. {// scope
  888. autostring32 oldText = GuiText_getString (me);
  889. for (integer i = 0; i < from_pos; i ++) if (oldText [i] > 0xFFFF) numberOfLeadingHighUnicodeValues ++;
  890. for (integer i = from_pos; i < to_pos; i ++) if (oldText [i] > 0xFFFF) numberOfSelectedHighUnicodeValues ++;
  891. }
  892. from_pos += numberOfLeadingHighUnicodeValues;
  893. to_pos += numberOfLeadingHighUnicodeValues + numberOfSelectedHighUnicodeValues;
  894. NSRange nsRange = NSMakeRange (integer_to_uinteger (from_pos), integer_to_uinteger (to_pos - from_pos));
  895. NSString *nsString = (NSString *) Melder_peek32toCfstring (text);
  896. [my d_cocoaTextView shouldChangeTextInRange: nsRange replacementString: nsString]; // ignore the returned BOOL: only interested in the side effect of having undo support
  897. [[my d_cocoaTextView textStorage] replaceCharactersInRange: nsRange withString: nsString];
  898. }
  899. #endif
  900. }
  901. void GuiText_scrollToSelection (GuiText me) {
  902. #if gtk
  903. GtkTextBuffer *textBuffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (my d_widget));
  904. GtkTextIter start, end;
  905. gtk_text_buffer_get_selection_bounds (textBuffer, & start, & end);
  906. //GtkTextMark *mark = gtk_text_buffer_create_mark (textBuffer, nullptr, & start, true);
  907. gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (my d_widget), & start, 0.1, false, 0.0, 0.0);
  908. //gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (my d_widget), mark, 0.1, false, 0.0, 0.0);
  909. #elif motif
  910. Edit_ScrollCaret (my d_widget -> window);
  911. #elif cocoa
  912. if (my d_cocoaTextView)
  913. [my d_cocoaTextView scrollRangeToVisible: [my d_cocoaTextView selectedRange]];
  914. #endif
  915. }
  916. void GuiText_setChangedCallback (GuiText me, GuiText_ChangedCallback changedCallback, Thing changedBoss) {
  917. my d_changedCallback = changedCallback;
  918. my d_changedBoss = changedBoss;
  919. }
  920. void GuiText_setFontSize (GuiText me, int size) {
  921. #if gtk
  922. GtkRcStyle *modStyle = gtk_widget_get_modifier_style (GTK_WIDGET (my d_widget));
  923. trace (U"before initializing Pango: locale is ", Melder_peek8to32 (setlocale (LC_ALL, nullptr)));
  924. PangoFontDescription *fontDesc = modStyle -> font_desc != nullptr ? modStyle -> font_desc :
  925. #if ALLOW_GDK_DRAWING
  926. pango_font_description_copy (GTK_WIDGET (my d_widget) -> style -> font_desc);
  927. #else
  928. nullptr;
  929. #endif
  930. trace (U"during initializing Pango: locale is ", Melder_peek8to32 (setlocale (LC_ALL, nullptr)));
  931. pango_font_description_set_absolute_size (fontDesc, size * PANGO_SCALE);
  932. trace (U"after initializing Pango: locale is ", Melder_peek8to32 (setlocale (LC_ALL, nullptr)));
  933. modStyle -> font_desc = fontDesc;
  934. gtk_widget_modify_style (GTK_WIDGET (my d_widget), modStyle);
  935. #elif motif
  936. // a trick to update the window. BUG: why doesn't UpdateWindow seem to suffice?
  937. integer first, last;
  938. autostring32 text = GuiText_getStringAndSelectionPosition (me, & first, & last);
  939. GuiText_setString (me, U""); // erase all
  940. UpdateWindow (my d_widget -> window);
  941. if (size <= 10) {
  942. SetWindowFont (my d_widget -> window, font10, false);
  943. } else if (size <= 12) {
  944. SetWindowFont (my d_widget -> window, font12, false);
  945. } else if (size <= 14) {
  946. SetWindowFont (my d_widget -> window, font14, false);
  947. } else if (size <= 18) {
  948. SetWindowFont (my d_widget -> window, font18, false);
  949. } else {
  950. SetWindowFont (my d_widget -> window, font24, false);
  951. }
  952. GuiText_setString (me, text.get());
  953. GuiText_setSelection (me, first, last);
  954. UpdateWindow (my d_widget -> window);
  955. #elif cocoa
  956. if (my d_cocoaTextView) {
  957. [my d_cocoaTextView setFont: [NSFont fontWithName: @"Menlo" size: size]];
  958. }
  959. #endif
  960. }
  961. void GuiText_setRedoItem (GuiText me, GuiMenuItem item) {
  962. #if gtk
  963. if (my d_redo_item)
  964. //g_object_unref (my d_redo_item -> d_widget);
  965. my d_redo_item = item;
  966. if (my d_redo_item) {
  967. //g_object_ref (my d_redo_item -> d_widget);
  968. GuiThing_setSensitive (my d_redo_item, history_has_redo (me));
  969. }
  970. #elif motif
  971. #elif cocoa
  972. #endif
  973. }
  974. void GuiText_setSelection (GuiText me, integer first, integer last) {
  975. if (my d_widget) {
  976. #if gtk
  977. if (G_OBJECT_TYPE (G_OBJECT (my d_widget)) == GTK_TYPE_ENTRY) {
  978. gtk_editable_select_region (GTK_EDITABLE (my d_widget), first, last);
  979. } else if (G_OBJECT_TYPE (G_OBJECT (my d_widget)) == GTK_TYPE_TEXT_VIEW) {
  980. GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (my d_widget));
  981. GtkTextIter from_it, to_it;
  982. gtk_text_buffer_get_iter_at_offset (buffer, & from_it, first);
  983. gtk_text_buffer_get_iter_at_offset (buffer, & to_it, last);
  984. gtk_text_buffer_select_range (buffer, & from_it, & to_it);
  985. }
  986. #elif motif
  987. autostring32 text = GuiText_getString (me);
  988. if (first < 0) first = 0;
  989. if (last < 0) last = 0;
  990. integer length = str32len (text.get());
  991. if (first >= length) first = length;
  992. if (last >= length) last = length;
  993. /*
  994. * 'first' and 'last' are the positions of the selection in the text when separated by LF alone.
  995. * We have to convert this to the positions that the selection has in a text separated by CR/LF sequences.
  996. */
  997. integer numberOfLeadingLineBreaks = 0, numberOfSelectedLineBreaks = 0;
  998. for (integer i = 0; i < first; i ++) if (text [i] == U'\n') numberOfLeadingLineBreaks ++;
  999. for (integer i = first; i < last; i ++) if (text [i] == U'\n') numberOfSelectedLineBreaks ++;
  1000. /*
  1001. On Windows, characters are counted in UTF-16 units, whereas 'first' and 'last' are in UTF-32 units. Convert.
  1002. */
  1003. integer numberOfLeadingHighUnicodeValues = 0, numberOfSelectedHighUnicodeValues = 0;
  1004. for (integer i = 0; i < first; i ++) if (text [i] > 0xFFFF) numberOfLeadingHighUnicodeValues ++;
  1005. for (integer i = first; i < last; i ++) if (text [i] > 0xFFFF) numberOfSelectedHighUnicodeValues ++;
  1006. first += numberOfLeadingLineBreaks;
  1007. last += numberOfLeadingLineBreaks + numberOfSelectedLineBreaks;
  1008. first += numberOfLeadingHighUnicodeValues;
  1009. last += numberOfLeadingHighUnicodeValues + numberOfSelectedHighUnicodeValues;
  1010. Edit_SetSel (my d_widget -> window, first, last);
  1011. UpdateWindow (my d_widget -> window);
  1012. #elif cocoa
  1013. /*
  1014. On Cocoa, characters are counted in UTF-16 units, whereas 'first' and 'last' are in UTF-32 units. Convert.
  1015. */
  1016. autostring32 text = GuiText_getString (me);
  1017. integer numberOfLeadingHighUnicodeValues = 0, numberOfSelectedHighUnicodeValues = 0;
  1018. for (integer i = 0; i < first; i ++) if (text [i] > 0xFFFF) numberOfLeadingHighUnicodeValues ++;
  1019. for (integer i = first; i < last; i ++) if (text [i] > 0xFFFF) numberOfSelectedHighUnicodeValues ++;
  1020. first += numberOfLeadingHighUnicodeValues;
  1021. last += numberOfLeadingHighUnicodeValues + numberOfSelectedHighUnicodeValues;
  1022. if (my d_cocoaTextView) {
  1023. [my d_cocoaTextView setSelectedRange: NSMakeRange (integer_to_uinteger (first), integer_to_uinteger (last - first))];
  1024. }
  1025. #endif
  1026. }
  1027. }
  1028. void GuiText_setString (GuiText me, conststring32 text) {
  1029. #if gtk
  1030. if (G_OBJECT_TYPE (my d_widget) == GTK_TYPE_ENTRY) {
  1031. gtk_entry_set_text (GTK_ENTRY (my d_widget), Melder_peek32to8 (text));
  1032. } else if (G_OBJECT_TYPE (my d_widget) == GTK_TYPE_TEXT_VIEW) {
  1033. GtkTextBuffer *textBuffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (my d_widget));
  1034. const gchar *textUtf8 = Melder_peek32to8 (text);
  1035. //gtk_text_buffer_set_text (textBuffer, textUtf8, strlen (textUtf8)); // length in bytes!
  1036. GtkTextIter start, end;
  1037. gtk_text_buffer_get_start_iter (textBuffer, & start);
  1038. gtk_text_buffer_get_end_iter (textBuffer, & end);
  1039. gtk_text_buffer_delete_interactive (textBuffer, & start, & end, gtk_text_view_get_editable (GTK_TEXT_VIEW (my d_widget)));
  1040. gtk_text_buffer_insert_interactive (textBuffer, & start, textUtf8, strlen (textUtf8), gtk_text_view_get_editable (GTK_TEXT_VIEW (my d_widget)));
  1041. }
  1042. #elif motif
  1043. autostring32 winText (2 * str32len (text), true); // all new lines
  1044. char32 *to = & winText [0];
  1045. /*
  1046. Replace all LF with CR/LF.
  1047. */
  1048. for (const char32 *from = & text [0]; *from != U'\0'; from ++, to ++)
  1049. if (*from == U'\n') { *to = 13; * ++ to = U'\n'; } else *to = *from;
  1050. *to = U'\0';
  1051. SetWindowTextW (my d_widget -> window, Melder_peek32toW (winText.get()));
  1052. UpdateWindow (my d_widget -> window);
  1053. #elif cocoa
  1054. trace (U"title");
  1055. if (my d_cocoaTextView) {
  1056. NSRange nsRange = NSMakeRange (0, [[my d_cocoaTextView textStorage] length]);
  1057. NSString *nsString = (NSString *) Melder_peek32toCfstring (text);
  1058. [my d_cocoaTextView shouldChangeTextInRange: nsRange replacementString: nsString]; // to make this action undoable
  1059. //[[my d_cocoaTextView textStorage] replaceCharactersInRange: nsRange withString: nsString];
  1060. [my d_cocoaTextView setString: nsString];
  1061. [my d_cocoaTextView scrollRangeToVisible: NSMakeRange ([[my d_cocoaTextView textStorage] length], 0)]; // to the end
  1062. //[[my d_cocoaTextView window] setViewsNeedDisplay: YES];
  1063. //[[my d_cocoaTextView window] display];
  1064. } else {
  1065. [(NSTextField *) my d_widget setStringValue: (NSString *) Melder_peek32toCfstring (text)];
  1066. }
  1067. #endif
  1068. }
  1069. void GuiText_setUndoItem (GuiText me, GuiMenuItem item) {
  1070. #if gtk
  1071. if (my d_undo_item) {
  1072. //g_object_unref (my d_undo_item -> d_widget);
  1073. }
  1074. my d_undo_item = item;
  1075. if (my d_undo_item) {
  1076. //g_object_ref (my d_undo_item -> d_widget);
  1077. GuiThing_setSensitive (my d_undo_item, history_has_undo (me));
  1078. }
  1079. #elif motif
  1080. #elif cocoa
  1081. #endif
  1082. }
  1083. void GuiText_undo (GuiText me) {
  1084. #if gtk
  1085. history_do (me, 1);
  1086. #elif motif
  1087. #elif cocoa
  1088. if (my d_cocoaTextView) {
  1089. [[my d_cocoaTextView undoManager] undo];
  1090. }
  1091. #endif
  1092. }
  1093. /* End of file GuiText.cpp */