1 /* Editor.cpp
2  *
3  * Copyright (C) 1992-2021 Paul Boersma, 2008 Stefan de Konink, 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 
19 #include <time.h>
20 #include "ScriptEditor.h"
21 #include "machine.h"
22 #include "EditorM.h"
23 #include "praat_script.h"
24 #include "sendsocket.h"
25 
26 #include "enums_getText.h"
27 #include "Editor_enums.h"
28 #include "enums_getValue.h"
29 #include "Editor_enums.h"
30 
31 Thing_implement (Editor, Thing, 0);
32 
33 #include "prefs_define.h"
34 #include "Editor_prefs.h"
35 #include "prefs_install.h"
36 #include "Editor_prefs.h"
37 #include "prefs_copyToInstance.h"
38 #include "Editor_prefs.h"
39 
40 /********** class EditorCommand **********/
41 
42 Thing_implement (EditorCommand, Thing, 0);
43 
44 /********** class EditorMenu **********/
45 
46 Thing_implement (EditorMenu, Thing, 0);
47 
48 /********** functions **********/
49 
commonCallback(EditorCommand me,GuiMenuItemEvent)50 static void commonCallback (EditorCommand me, GuiMenuItemEvent /* event */) {
51 	if (my d_editor && my d_editor -> v_scriptable () && ! str32str (my itemTitle.get(), U"...")) {
52 		UiHistory_write (U"\n");
53 		UiHistory_write_colonize (my itemTitle.get());
54 	}
55 	try {
56 		my commandCallback (my d_editor, me, nullptr, 0, nullptr, nullptr, nullptr);
57 	} catch (MelderError) {
58 		if (! Melder_hasError (U"Script exited."))
59 			Melder_appendError (U"Menu command \"", my itemTitle.get(), U"\" not completed.");
60 		Melder_flushError ();
61 	}
62 }
63 
EditorMenu_addCommand(EditorMenu me,conststring32 itemTitle,uint32 flags,EditorCommandCallback commandCallback)64 GuiMenuItem EditorMenu_addCommand (EditorMenu me, conststring32 itemTitle /* cattable */, uint32 flags, EditorCommandCallback commandCallback)
65 {
66 	autoEditorCommand thee = Thing_new (EditorCommand);
67 	thy d_editor = my d_editor;
68 	thy menu = me;
69 	thy itemTitle = Melder_dup (itemTitle);
70 	thy itemWidget =
71 			! commandCallback ? GuiMenu_addSeparator (my menuWidget) :
72 			flags & Editor_HIDDEN ? nullptr :
73 			GuiMenu_addItem (my menuWidget, itemTitle, flags, commonCallback, thee.get());   // DANGLE BUG: me can be killed by Collection_addItem(), but EditorCommand::destroy doesn't remove the item
74 	thy commandCallback = commandCallback;
75 	GuiMenuItem result = thy itemWidget;
76 	my commands. addItem_move (thee.move());
77 	return result;
78 }
79 
80 /*GuiObject EditorCommand_getItemWidget (EditorCommand me) { return my itemWidget; }*/
81 
Editor_addMenu(Editor me,conststring32 menuTitle,uint32 flags)82 EditorMenu Editor_addMenu (Editor me, conststring32 menuTitle, uint32 flags) {
83 	autoEditorMenu thee = Thing_new (EditorMenu);
84 	thy d_editor = me;
85 	thy menuTitle = Melder_dup (menuTitle);
86 	thy menuWidget = GuiMenu_createInWindow (my windowForm, menuTitle, flags);
87 	return my menus. addItem_move (thee.move());
88 }
89 
90 /*GuiObject EditorMenu_getMenuWidget (EditorMenu me) { return my menuWidget; }*/
91 
Editor_addCommand(Editor me,conststring32 menuTitle,conststring32 itemTitle,uint32 flags,EditorCommandCallback commandCallback)92 GuiMenuItem Editor_addCommand (Editor me, conststring32 menuTitle, conststring32 itemTitle, uint32 flags, EditorCommandCallback commandCallback)
93 {
94 	try {
95 		integer numberOfMenus = my menus.size;
96 		for (integer imenu = 1; imenu <= numberOfMenus; imenu ++) {
97 			EditorMenu menu = my menus.at [imenu];
98 			if (str32equ (menuTitle, menu -> menuTitle.get()))
99 				return EditorMenu_addCommand (menu, itemTitle, flags, commandCallback);
100 		}
101 		Melder_throw (U"Menu \"", menuTitle, U"\" does not exist.");
102 	} catch (MelderError) {
103 		Melder_throw (U"Command \"", itemTitle, U"\" not inserted in menu \"", menuTitle, U".");
104 	}
105 }
106 
Editor_scriptCallback(Editor me,EditorCommand cmd,UiForm,integer,Stackel,conststring32,Interpreter)107 static void Editor_scriptCallback (Editor me, EditorCommand cmd, UiForm /* sendingForm */,
108 	integer /* narg */, Stackel /* args */, conststring32 /* sendingString */, Interpreter /* interpreter */)
109 {
110 	DO_RunTheScriptFromAnyAddedEditorCommand (me, cmd -> script.get());
111 }
112 
Editor_addCommandScript(Editor me,conststring32 menuTitle,conststring32 itemTitle,uint32 flags,conststring32 script)113 GuiMenuItem Editor_addCommandScript (Editor me, conststring32 menuTitle, conststring32 itemTitle, uint32 flags,
114 	conststring32 script)
115 {
116 	integer numberOfMenus = my menus.size;
117 	for (integer imenu = 1; imenu <= numberOfMenus; imenu ++) {
118 		EditorMenu menu = my menus.at [imenu];
119 		if (str32equ (menuTitle, menu -> menuTitle.get())) {
120 			autoEditorCommand cmd = Thing_new (EditorCommand);
121 			cmd -> d_editor = me;
122 			cmd -> menu = menu;
123 			cmd -> itemTitle = Melder_dup (itemTitle);
124 			cmd -> itemWidget = script == nullptr ? GuiMenu_addSeparator (menu -> menuWidget) :
125 				GuiMenu_addItem (menu -> menuWidget, itemTitle, flags, commonCallback, cmd.get());   // DANGLE BUG
126 			cmd -> commandCallback = Editor_scriptCallback;
127 			if (script [0] == U'\0') {
128 				cmd -> script = Melder_dup (U"");
129 			} else {
130 				structMelderFile file { };
131 				Melder_relativePathToFile (script, & file);
132 				cmd -> script = Melder_dup (Melder_fileToPath (& file));
133 			}
134 			GuiMenuItem result = cmd -> itemWidget;
135 			menu -> commands. addItem_move (cmd.move());
136 			return result;
137 		}
138 	}
139 	Melder_warning (
140 		U"Menu \"", menuTitle, U"\" does not exist.\n"
141 		U"Command \"", itemTitle, U"\" not inserted in menu \"", menuTitle, U".\n"
142 		U"To fix this, go to Praat->Preferences->Buttons->Editors, and remove the script from this menu.\n"
143 		U"You may want to install the script in a different menu."
144 	);
145 	return nullptr;
146 }
147 
Editor_setMenuSensitive(Editor me,conststring32 menuTitle,bool sensitive)148 void Editor_setMenuSensitive (Editor me, conststring32 menuTitle, bool sensitive) {
149 	integer numberOfMenus = my menus.size;
150 	for (integer imenu = 1; imenu <= numberOfMenus; imenu ++) {
151 		EditorMenu menu = my menus.at [imenu];
152 		if (str32equ (menuTitle, menu -> menuTitle.get())) {
153 			GuiThing_setSensitive (menu -> menuWidget, sensitive);
154 			return;
155 		}
156 	}
157 }
158 
Editor_getMenuCommand(Editor me,conststring32 menuTitle,conststring32 itemTitle)159 EditorCommand Editor_getMenuCommand (Editor me, conststring32 menuTitle, conststring32 itemTitle) {
160 	integer numberOfMenus = my menus.size;
161 	for (int imenu = 1; imenu <= numberOfMenus; imenu ++) {
162 		EditorMenu menu = my menus.at [imenu];
163 		if (str32equ (menuTitle, menu -> menuTitle.get())) {
164 			integer numberOfCommands = menu -> commands.size, icommand;
165 			for (icommand = 1; icommand <= numberOfCommands; icommand ++) {
166 				EditorCommand command = menu -> commands.at [icommand];
167 				if (str32equ (itemTitle, command -> itemTitle.get()))
168 					return command;
169 			}
170 		}
171 	}
172 	Melder_throw (U"Command \"", itemTitle, U"\" not found in menu \"", menuTitle, U"\".");
173 }
174 
Editor_doMenuCommand(Editor me,conststring32 commandTitle,integer narg,Stackel args,conststring32 arguments,Interpreter interpreter)175 void Editor_doMenuCommand (Editor me, conststring32 commandTitle, integer narg, Stackel args, conststring32 arguments, Interpreter interpreter) {
176 	integer numberOfMenus = my menus.size;
177 	for (int imenu = 1; imenu <= numberOfMenus; imenu ++) {
178 		EditorMenu menu = my menus.at [imenu];
179 		integer numberOfCommands = menu -> commands.size;
180 		for (integer icommand = 1; icommand <= numberOfCommands; icommand ++) {
181 			EditorCommand command = menu -> commands.at [icommand];
182 			if (str32equ (commandTitle, command -> itemTitle.get())) {
183 				command -> commandCallback (me, command, nullptr, narg, args, arguments, interpreter);
184 				return;
185 			}
186 		}
187 	}
188 	Melder_throw (U"Command not available in ", my classInfo -> className, U".");
189 }
190 
191 /********** class Editor **********/
192 
v_destroy()193 void structEditor :: v_destroy () noexcept {
194 	trace (U"enter");
195 	MelderAudio_stopPlaying (MelderAudio_IMPLICIT);
196 	/*
197 		The following command must be performed before the shell is destroyed.
198 		Otherwise, we would be forgetting dangling command dialogs here.
199 	*/
200 	our menus.removeAllItems();
201 
202 	Editor_broadcastDestruction (this);
203 	if (our windowForm) {
204 		#if gtk
205 			if (our windowForm -> d_gtkWindow) {
206 				Melder_assert (GTK_IS_WIDGET (our windowForm -> d_gtkWindow));
207 				gtk_widget_destroy (GTK_WIDGET (our windowForm -> d_gtkWindow));
208 			}
209 		#elif motif
210 			if (our windowForm -> d_xmShell) {
211 				XtDestroyWidget (our windowForm -> d_xmShell);
212 			}
213 		#elif cocoa
214 			if (our windowForm -> drawingArea) {
215 				GuiCocoaDrawingArea *cocoaDrawingArea = (GuiCocoaDrawingArea *) our windowForm -> drawingArea -> d_widget;
216 				if (cocoaDrawingArea)
217 					[cocoaDrawingArea   setUserData: nullptr];
218 			}
219 			if (our windowForm -> d_cocoaShell) {
220 				NSWindow *cocoaWindow = our windowForm -> d_cocoaShell;
221 				//our windowForm -> d_cocoaShell = nullptr;
222 				[cocoaWindow close];   // this should *release* the window as well, because `releasedWhenClosed` is on by default for NSWindows
223 			}
224 		#endif
225 	}
226 	if (our ownData)
227 		forget (our data);
228 	Editor_Parent :: v_destroy ();
229 }
230 
v_info()231 void structEditor :: v_info () {
232 	MelderInfo_writeLine (U"Editor type: ", Thing_className (this));
233 	MelderInfo_writeLine (U"Editor name: ", our name ? our name.get() : U"<no name>");
234 	time_t today = time (nullptr);
235 	MelderInfo_writeLine (U"Date: ", Melder_peek8to32 (ctime (& today)));   // includes a newline
236 	if (our data) {
237 		MelderInfo_writeLine (U"Data type: ", our data -> classInfo -> className);
238 		MelderInfo_writeLine (U"Data name: ", our data -> name.get());
239 	}
240 }
241 
v_nameChanged()242 void structEditor :: v_nameChanged () {
243 	if (our name)
244 		GuiShell_setTitle (our windowForm, our name.get());
245 }
246 
v_saveData()247 void structEditor :: v_saveData () {
248 	if (! our data)
249 		return;
250 	our previousData = Data_copy (our data);
251 }
252 
v_restoreData()253 void structEditor :: v_restoreData () {
254 	if (our data && our previousData)
255 		Thing_swap (our data, our previousData.get());
256 }
257 
menu_cb_sendBackToCallingProgram(Editor me,EDITOR_ARGS_DIRECT)258 static void menu_cb_sendBackToCallingProgram (Editor me, EDITOR_ARGS_DIRECT) {
259 	if (my data) {
260 		structMelderFile file { };
261 		MelderDir_getFile (& Melder_preferencesFolder, U"praat_backToCaller.Data", & file);
262 		Data_writeToTextFile (my data, & file);
263 		sendsocket (Melder_peek32to8 (my callbackSocket.get()), Melder_peek32to8 (my data -> name.get()));
264 	}
265 	my v_goAway ();
266 }
267 
menu_cb_close(Editor me,EDITOR_ARGS_DIRECT)268 static void menu_cb_close (Editor me, EDITOR_ARGS_DIRECT) {
269 	my v_goAway ();
270 }
271 
menu_cb_undo(Editor me,EDITOR_ARGS_DIRECT)272 static void menu_cb_undo (Editor me, EDITOR_ARGS_DIRECT) {
273 	my v_restoreData ();
274 	if (str32nequ (my undoText, U"Undo", 4)) my undoText [0] = U'R', my undoText [1] = U'e';
275 	else if (str32nequ (my undoText, U"Redo", 4)) my undoText [0] = U'U', my undoText [1] = U'n';
276 	else str32cpy (my undoText, U"Undo?");
277 	#if gtk
278 		gtk_label_set_label (GTK_LABEL (gtk_bin_get_child (GTK_BIN (my undoButton -> d_widget))), Melder_peek32to8 (my undoText));
279 	#elif motif
280 		conststring8 text_utf8 = Melder_peek32to8 (my undoText);
281 		XtVaSetValues (my undoButton -> d_widget, XmNlabelString, text_utf8, nullptr);
282 	#elif cocoa
283 		[(GuiCocoaMenuItem *) my undoButton -> d_widget   setTitle: (NSString *) Melder_peek32toCfstring (my undoText)];
284 	#endif
285 	/*
286 		Send a message to myself (e.g., I will redraw myself).
287 	*/
288 	my v_dataChanged ();
289 	/*
290 		Send a message to my boss (e.g., she will notify the others that depend on me).
291 	*/
292 	Editor_broadcastDataChanged (me);
293 }
294 
menu_cb_searchManual(Editor,EDITOR_ARGS_DIRECT)295 static void menu_cb_searchManual (Editor /* me */, EDITOR_ARGS_DIRECT) {
296 	Melder_search ();
297 }
298 
menu_cb_newScript(Editor me,EDITOR_ARGS_DIRECT)299 static void menu_cb_newScript (Editor me, EDITOR_ARGS_DIRECT) {
300 	autoScriptEditor scriptEditor = ScriptEditor_createFromText (me, nullptr);
301 	scriptEditor.releaseToUser();
302 }
303 
menu_cb_openScript(Editor me,EDITOR_ARGS_DIRECT)304 static void menu_cb_openScript (Editor me, EDITOR_ARGS_DIRECT) {
305 	autoScriptEditor scriptEditor = ScriptEditor_createFromText (me, nullptr);
306 	TextEditor_showOpen (scriptEditor.get());
307 	scriptEditor.releaseToUser();
308 }
309 
v_createMenuItems_file(EditorMenu)310 void structEditor :: v_createMenuItems_file (EditorMenu /* menu */) {
311 }
312 
v_createMenuItems_edit(EditorMenu menu)313 void structEditor :: v_createMenuItems_edit (EditorMenu menu) {
314 	if (our data)
315 		our undoButton = EditorMenu_addCommand (menu, U"Cannot undo", GuiMenu_INSENSITIVE + 'Z', menu_cb_undo);
316 }
317 
INFO_EDITOR__settingsReport(Editor me,EDITOR_ARGS_DIRECT_WITH_OUTPUT)318 static void INFO_EDITOR__settingsReport (Editor me, EDITOR_ARGS_DIRECT_WITH_OUTPUT) {
319 	INFO_EDITOR
320 		Thing_info (me);
321 	INFO_EDITOR_END
322 }
323 
INFO_DATA__info(Editor me,EDITOR_ARGS_DIRECT_WITH_OUTPUT)324 static void INFO_DATA__info (Editor me, EDITOR_ARGS_DIRECT_WITH_OUTPUT) {
325 	INFO_DATA
326 		Thing_info (my data);
327 	INFO_DATA_END
328 }
329 
v_createMenuItems_query(EditorMenu menu)330 void structEditor :: v_createMenuItems_query (EditorMenu menu) {
331 	v_createMenuItems_query_info (menu);
332 }
333 
v_createMenuItems_query_info(EditorMenu menu)334 void structEditor :: v_createMenuItems_query_info (EditorMenu menu) {
335 	EditorMenu_addCommand (menu, U"Editor info", 0, INFO_EDITOR__settingsReport);
336 	EditorMenu_addCommand (menu, U"Settings report", Editor_HIDDEN, INFO_EDITOR__settingsReport);
337 	if (our data)
338 		EditorMenu_addCommand (menu, Melder_cat (Thing_className (data), U" info"), 0, INFO_DATA__info);
339 }
340 
v_createMenus()341 void structEditor :: v_createMenus () {
342 	EditorMenu menu = Editor_addMenu (this, U"File", 0);
343 	v_createMenuItems_file (menu);
344 	if (v_editable ()) {
345 		menu = Editor_addMenu (this, U"Edit", 0);
346 		v_createMenuItems_edit (menu);
347 	}
348 	if (v_hasQueryMenu ()) {
349 		menu = Editor_addMenu (this, U"Query", 0);
350 		v_createMenuItems_query (menu);
351 	}
352 }
353 
BOOLEAN_VARIABLE(v_form_pictureWindow_eraseFirst)354 BOOLEAN_VARIABLE (v_form_pictureWindow_eraseFirst)
355 void structEditor :: v_form_pictureWindow (EditorCommand cmd) {
356 	LABEL (U"Picture window:")
357 	BOOLEAN_FIELD (v_form_pictureWindow_eraseFirst, U"Erase first", true)
358 }
v_ok_pictureWindow(EditorCommand cmd)359 void structEditor :: v_ok_pictureWindow (EditorCommand cmd) {
360 	SET_BOOLEAN (v_form_pictureWindow_eraseFirst, our pref_picture_eraseFirst ())
361 }
v_do_pictureWindow(EditorCommand)362 void structEditor :: v_do_pictureWindow (EditorCommand /* cmd */) {
363 	our pref_picture_eraseFirst () = v_form_pictureWindow_eraseFirst;
364 }
365 
OPTIONMENU_ENUM_VARIABLE(kEditor_writeNameAtTop,v_form_pictureMargins_writeNameAtTop)366 OPTIONMENU_ENUM_VARIABLE (kEditor_writeNameAtTop, v_form_pictureMargins_writeNameAtTop)
367 void structEditor :: v_form_pictureMargins (EditorCommand cmd) {
368 	LABEL (U"Margins:")
369 	OPTIONMENU_ENUM_FIELD (kEditor_writeNameAtTop, v_form_pictureMargins_writeNameAtTop,
370 			U"Write name at top", kEditor_writeNameAtTop::DEFAULT)
371 }
v_ok_pictureMargins(EditorCommand cmd)372 void structEditor :: v_ok_pictureMargins (EditorCommand cmd) {
373 	SET_ENUM (v_form_pictureMargins_writeNameAtTop, kEditor_writeNameAtTop, pref_picture_writeNameAtTop ())
374 }
v_do_pictureMargins(EditorCommand)375 void structEditor :: v_do_pictureMargins (EditorCommand /* cmd */) {
376 	pref_picture_writeNameAtTop () = v_form_pictureMargins_writeNameAtTop;
377 }
378 
gui_window_cb_goAway(Editor me)379 static void gui_window_cb_goAway (Editor me) {
380 	Melder_assert (me);
381 	Melder_assert (Thing_isa (me, classEditor));
382 	my v_goAway ();
383 }
384 
385 void praat_addCommandsToEditor (Editor me);
Editor_init(Editor me,int x,int y,int width,int height,conststring32 title,Daata data)386 void Editor_init (Editor me, int x, int y, int width, int height, conststring32 title, Daata data) {
387 	double xmin, ymin, widthmax, heightmax;
388 	Gui_getWindowPositioningBounds (& xmin, & ymin, & widthmax, & heightmax);
389 	/*
390 		Negative widths are relative to the whole screen.
391 	*/
392 	if (width < 0)
393 		width += (int) widthmax;
394 	if (height < 0)
395 		height += (int) heightmax;
396 	/*
397 		Don't start with a maximized window, because then the user doesn't know what a click on the maximize button means.
398 	*/
399 	Melder_clipRight (& width, (int) widthmax - 100);
400 	Melder_clipRight (& height, (int) heightmax - 100);
401 	/*
402 		Make sure that the window has at least a sane size,
403 		just in case the user made the previous window very small (Praat's FunctionEditor saves the last size),
404 		or the user edited the preferences file (which might save a window size).
405 	*/
406 	Melder_clipLeft (200, & width);
407 	Melder_clipLeft (150, & height);
408 	/*
409 		Now that the size is right, establish the position.
410 	*/
411 	int left, right, top, bottom;
412 	if (x > 0) {
413 		/*
414 			Positive x: relative to the left edge of the screen.
415 		*/
416 		left = (int) xmin + x;
417 		right = left + width;
418 	} else if (x < 0) {
419 		/*
420 			Negative x: relative to the right edge of the screen.
421 		*/
422 		right = (int) xmin + (int) widthmax + x;
423 		left = right - width;
424 	} else {
425 		/*
426 			Zero x: randomize between the left and right edge of the screen.
427 		*/
428 		left = (int) NUMrandomInteger ((int) xmin + 4, (int) xmin + (int) widthmax - width - 4);
429 		right = left + width;
430 	}
431 	if (y > 0) {
432 		/*
433 			Positive y: relative to the top of the screen.
434 		*/
435 		top = (int) ymin + y;
436 		bottom = top + height;
437 	} else if (y < 0) {
438 		/*
439 			Negative y: relative to the bottom of the screen.
440 		*/
441 		bottom = (int) ymin + (int) heightmax + y;
442 		top = bottom - height;
443 	} else {
444 		/*
445 			Zero y: randomize between the top and bottom of the screen.
446 		*/
447 		top = (int) NUMrandomInteger ((int) ymin + 4, (int) ymin + (int) heightmax - height - 4);
448 		//Melder_casual (ymin, U" ", heightmax, U" ", height, U" ", top);
449 		bottom = top + height;
450 	}
451 	#if defined (macintoshXXX) || gtk
452 		top += Machine_getTitleBarHeight ();
453 		bottom += Machine_getTitleBarHeight ();
454 	#endif
455 	my windowForm = GuiWindow_create (left, top, width, height, 450, 350, title, gui_window_cb_goAway, me, my v_canFullScreen () ? GuiWindow_FULLSCREEN : 0);
456 	Thing_setName (me, title);
457 	my data = data;
458 	my v_copyPreferencesToInstance ();
459 
460 	/*
461 		Create menus.
462 	*/
463 
464 	if (my v_hasMenuBar ())
465 		GuiWindow_addMenuBar (my windowForm);
466 
467 	my v_createChildren ();
468 
469 	if (my v_hasMenuBar ()) {
470 		my v_createMenus ();
471 		EditorMenu helpMenu = Editor_addMenu (me, U"Help", 0);
472 		my v_createHelpMenuItems (helpMenu);
473 		EditorMenu_addCommand (helpMenu, U"-- search --", 0, nullptr);
474 		my searchButton = EditorMenu_addCommand (helpMenu, U"Search manual...", 'M', menu_cb_searchManual);
475 		if (my v_scriptable ()) {
476 			Editor_addCommand (me, U"File", U"New editor script", 0, menu_cb_newScript);
477 			Editor_addCommand (me, U"File", U"Open editor script...", 0, menu_cb_openScript);
478 			Editor_addCommand (me, U"File", U"-- after script --", 0, 0);
479 		}
480 		/*
481 			Add the scripted commands.
482 		*/
483 		praat_addCommandsToEditor (me);
484 		if (my callbackSocket)
485 			Editor_addCommand (me, U"File", U"Send back to calling program", 0, menu_cb_sendBackToCallingProgram);
486 		Editor_addCommand (me, U"File", U"Close", 'W', menu_cb_close);
487 	}
488 	GuiThing_show (my windowForm);
489 }
490 
Editor_save(Editor me,conststring32 text)491 void Editor_save (Editor me, conststring32 text) {
492 	my v_saveData ();
493 	if (! my undoButton)
494 		return;
495 	GuiThing_setSensitive (my undoButton, true);
496 	Melder_sprint (my undoText,100, U"Undo ", text);
497 	#if gtk
498 		gtk_label_set_label (GTK_LABEL (gtk_bin_get_child (GTK_BIN (my undoButton -> d_widget))), Melder_peek32to8 (my undoText));
499 	#elif motif
500 		conststring8 text_utf8 = Melder_peek32to8 (my undoText);
501 		XtVaSetValues (my undoButton -> d_widget, XmNlabelString, text_utf8, nullptr);
502 	#elif cocoa
503 		[(GuiCocoaMenuItem *) my undoButton -> d_widget   setTitle: (NSString *) Melder_peek32toCfstring (my undoText)];
504 	#endif
505 }
506 
Editor_openPraatPicture(Editor me)507 void Editor_openPraatPicture (Editor me) {
508 	my pictureGraphics = praat_picture_editor_open (my pref_picture_eraseFirst ());
509 }
Editor_closePraatPicture(Editor me)510 void Editor_closePraatPicture (Editor me) {
511 	if (my data && my pref_picture_writeNameAtTop () != kEditor_writeNameAtTop::NO_) {
512 		Graphics_setNumberSignIsBold (my pictureGraphics, false);
513 		Graphics_setPercentSignIsItalic (my pictureGraphics, false);
514 		Graphics_setCircumflexIsSuperscript (my pictureGraphics, false);
515 		Graphics_setUnderscoreIsSubscript (my pictureGraphics, false);
516 		Graphics_textTop (my pictureGraphics,
517 			my pref_picture_writeNameAtTop () == kEditor_writeNameAtTop::FAR_,
518 			my data -> name.get()
519 		);
520 		Graphics_setNumberSignIsBold (my pictureGraphics, true);
521 		Graphics_setPercentSignIsItalic (my pictureGraphics, true);
522 		Graphics_setCircumflexIsSuperscript (my pictureGraphics, true);
523 		Graphics_setUnderscoreIsSubscript (my pictureGraphics, true);
524 	}
525 	praat_picture_editor_close ();
526 }
527 
528 /* End of file Editor.cpp */
529