1/*
2 * Copyright 2002-2020, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Mattias Sundblad
7 *		Andrew Bachmann
8 *		Philippe Saint-Pierre
9 *		Jonas Sundstr��m
10 *		Ryan Leavengood
11 *		Vlad Slepukhin
12 *		Sarzhuk Zharski
13 *		Pascal R. G. Abresch
14 */
15
16
17#include "ColorMenuItem.h"
18#include "Constants.h"
19#include "FindWindow.h"
20#include "ReplaceWindow.h"
21#include "StatusView.h"
22#include "StyledEditApp.h"
23#include "StyledEditView.h"
24#include "StyledEditWindow.h"
25
26#include <Alert.h>
27#include <Autolock.h>
28#include <Catalog.h>
29#include <CharacterSet.h>
30#include <CharacterSetRoster.h>
31#include <Clipboard.h>
32#include <Debug.h>
33#include <File.h>
34#include <FilePanel.h>
35#include <fs_attr.h>
36#include <LayoutBuilder.h>
37#include <Locale.h>
38#include <Menu.h>
39#include <MenuBar.h>
40#include <MenuItem.h>
41#include <NodeMonitor.h>
42#include <Path.h>
43#include <PrintJob.h>
44#include <RecentItems.h>
45#include <Rect.h>
46#include <Roster.h>
47#include <Screen.h>
48#include <ScrollView.h>
49#include <TextControl.h>
50#include <TextView.h>
51#include <TranslationUtils.h>
52#include <UnicodeChar.h>
53#include <UTF8.h>
54#include <Volume.h>
55
56
57using namespace BPrivate;
58
59
60const float kLineViewWidth = 30.0;
61const char* kInfoAttributeName = "StyledEdit-info";
62
63
64#undef B_TRANSLATION_CONTEXT
65#define B_TRANSLATION_CONTEXT "StyledEditWindow"
66
67
68// This is a temporary solution for building BString with printf like format.
69// will be removed in the future.
70static void
71bs_printf(BString* string, const char* format, ...)
72{
73	va_list ap;
74	va_start(ap, format);
75	char* buf;
76	vasprintf(&buf, format, ap);
77	string->SetTo(buf);
78	free(buf);
79	va_end(ap);
80}
81
82
83// #pragma mark -
84
85
86StyledEditWindow::StyledEditWindow(BRect frame, int32 id, uint32 encoding)
87	:
88	BWindow(frame, "untitled", B_DOCUMENT_WINDOW, B_ASYNCHRONOUS_CONTROLS
89		| B_AUTO_UPDATE_SIZE_LIMITS),
90	fFindWindow(NULL),
91	fReplaceWindow(NULL)
92{
93	_InitWindow(encoding);
94	BString unTitled(B_TRANSLATE("Untitled "));
95	unTitled << id;
96	SetTitle(unTitled.String());
97	fSaveItem->SetEnabled(true);
98		// allow saving empty files
99	Show();
100}
101
102
103StyledEditWindow::StyledEditWindow(BRect frame, entry_ref* ref, uint32 encoding)
104	:
105	BWindow(frame, "untitled", B_DOCUMENT_WINDOW, B_ASYNCHRONOUS_CONTROLS
106		| B_AUTO_UPDATE_SIZE_LIMITS),
107	fFindWindow(NULL),
108	fReplaceWindow(NULL)
109{
110	_InitWindow(encoding);
111	OpenFile(ref);
112	Show();
113}
114
115
116StyledEditWindow::~StyledEditWindow()
117{
118	delete fSaveMessage;
119	delete fPrintSettings;
120	delete fSavePanel;
121}
122
123
124void
125StyledEditWindow::Quit()
126{
127	_SwitchNodeMonitor(false);
128
129	_SaveAttrs();
130	if (StyledEditApp* app = dynamic_cast<StyledEditApp*>(be_app))
131		app->CloseDocument();
132	BWindow::Quit();
133}
134
135
136#undef B_TRANSLATION_CONTEXT
137#define B_TRANSLATION_CONTEXT "QuitAlert"
138
139
140bool
141StyledEditWindow::QuitRequested()
142{
143	if (fClean)
144		return true;
145
146	if (fTextView->TextLength() == 0 && fSaveMessage == NULL)
147		return true;
148
149	BString alertText;
150	bs_printf(&alertText,
151		B_TRANSLATE("Save changes to the document \"%s\"? "), Title());
152
153	int32 index = _ShowAlert(alertText, B_TRANSLATE("Cancel"),
154		B_TRANSLATE("Don't save"), B_TRANSLATE("Save"),	B_WARNING_ALERT);
155
156	if (index == 0)
157		return false;	// "cancel": dont save, dont close the window
158
159	if (index == 1)
160		return true;	// "don't save": just close the window
161
162	if (!fSaveMessage) {
163		SaveAs(new BMessage(SAVE_THEN_QUIT));
164		return false;
165	}
166
167	return Save() == B_OK;
168}
169
170
171void
172StyledEditWindow::MessageReceived(BMessage* message)
173{
174	if (message->WasDropped()) {
175		entry_ref ref;
176		if (message->FindRef("refs", 0, &ref)==B_OK) {
177			message->what = B_REFS_RECEIVED;
178			be_app->PostMessage(message);
179		}
180	}
181
182	switch (message->what) {
183		case MENU_NEW:
184			 // this is because of the layout menu change,
185			 // it is too early to connect to a different looper there
186			 // so either have to redirect it here, or change the invocation
187			be_app->PostMessage(message);
188			break;
189		// File menu
190		case MENU_SAVE:
191			if (!fSaveMessage)
192				SaveAs();
193			else
194				Save(fSaveMessage);
195			break;
196
197		case MENU_SAVEAS:
198			SaveAs();
199			break;
200
201		case B_SAVE_REQUESTED:
202			Save(message);
203			break;
204
205		case SAVE_THEN_QUIT:
206			if (Save(message) == B_OK)
207				Quit();
208			break;
209
210		case MENU_RELOAD:
211			_ReloadDocument(message);
212			break;
213
214		case MENU_CLOSE:
215			if (QuitRequested())
216				Quit();
217			break;
218
219		case MENU_PAGESETUP:
220			PageSetup(fTextView->Window()->Title());
221			break;
222		case MENU_PRINT:
223			Print(fTextView->Window()->Title());
224			break;
225		case MENU_QUIT:
226			be_app->PostMessage(B_QUIT_REQUESTED);
227			break;
228
229		// Edit menu
230
231		case B_UNDO:
232			ASSERT(fCanUndo || fCanRedo);
233			ASSERT(!(fCanUndo && fCanRedo));
234			if (fCanUndo)
235				fUndoFlag = true;
236			if (fCanRedo)
237				fRedoFlag = true;
238
239			fTextView->Undo(be_clipboard);
240			break;
241		case B_CUT:
242		case B_COPY:
243		case B_PASTE:
244		case B_SELECT_ALL:
245			fTextView->MessageReceived(message);
246			break;
247		case MENU_CLEAR:
248			fTextView->Clear();
249			break;
250		case MENU_FIND:
251		{
252			if (fFindWindow == NULL) {
253				BRect findWindowFrame(Frame());
254				findWindowFrame.InsetBy(
255					(findWindowFrame.Width() - 400) / 2,
256					(findWindowFrame.Height() - 235) / 2);
257
258				fFindWindow = new FindWindow(findWindowFrame, this,
259					&fStringToFind, fCaseSensitive, fWrapAround, fBackSearch);
260				fFindWindow->Show();
261
262			} else if (fFindWindow->IsHidden())
263				fFindWindow->Show();
264			else
265				fFindWindow->Activate();
266			break;
267		}
268		case MSG_FIND_WINDOW_QUIT:
269		{
270			fFindWindow = NULL;
271			Activate();
272				// In case any 'always on top' application tries to make its
273				// window active after fFindWindow is closed.
274			break;
275		}
276		case MSG_REPLACE_WINDOW_QUIT:
277		{
278			fReplaceWindow = NULL;
279			Activate();
280			break;
281		}
282		case MSG_SEARCH:
283			message->FindString("findtext", &fStringToFind);
284			fFindAgainItem->SetEnabled(true);
285			message->FindBool("casesens", &fCaseSensitive);
286			message->FindBool("wrap", &fWrapAround);
287			message->FindBool("backsearch", &fBackSearch);
288
289			_Search(fStringToFind, fCaseSensitive, fWrapAround, fBackSearch);
290			Activate();
291			break;
292		case MENU_FIND_AGAIN:
293			_Search(fStringToFind, fCaseSensitive, fWrapAround, fBackSearch);
294			break;
295		case MENU_FIND_SELECTION:
296			_FindSelection();
297			break;
298		case MENU_REPLACE:
299		{
300			if (fReplaceWindow == NULL) {
301				BRect replaceWindowFrame(Frame());
302				replaceWindowFrame.InsetBy(
303					(replaceWindowFrame.Width() - 400) / 2,
304					(replaceWindowFrame.Height() - 284) / 2);
305
306				fReplaceWindow = new ReplaceWindow(replaceWindowFrame, this,
307					&fStringToFind, &fReplaceString, fCaseSensitive,
308					fWrapAround, fBackSearch);
309				fReplaceWindow->Show();
310
311			} else if (fReplaceWindow->IsHidden())
312				fReplaceWindow->Show();
313			else
314				fReplaceWindow->Activate();
315			break;
316		}
317		case MSG_REPLACE:
318		{
319			message->FindBool("casesens", &fCaseSensitive);
320			message->FindBool("wrap", &fWrapAround);
321			message->FindBool("backsearch", &fBackSearch);
322
323			message->FindString("FindText", &fStringToFind);
324			message->FindString("ReplaceText", &fReplaceString);
325
326			fFindAgainItem->SetEnabled(true);
327			fReplaceSameItem->SetEnabled(true);
328
329			_Replace(fStringToFind, fReplaceString, fCaseSensitive, fWrapAround,
330				fBackSearch);
331			Activate();
332			break;
333		}
334		case MENU_REPLACE_SAME:
335			_Replace(fStringToFind, fReplaceString, fCaseSensitive, fWrapAround,
336				fBackSearch);
337			break;
338
339		case MSG_REPLACE_ALL:
340		{
341			message->FindBool("casesens", &fCaseSensitive);
342			message->FindString("FindText", &fStringToFind);
343			message->FindString("ReplaceText", &fReplaceString);
344
345			bool allWindows;
346			message->FindBool("allwindows", &allWindows);
347
348			fFindAgainItem->SetEnabled(true);
349			fReplaceSameItem->SetEnabled(true);
350
351			if (allWindows)
352				SearchAllWindows(fStringToFind, fReplaceString, fCaseSensitive);
353			else
354				_ReplaceAll(fStringToFind, fReplaceString, fCaseSensitive);
355			Activate();
356			break;
357		}
358
359		case B_NODE_MONITOR:
360			_HandleNodeMonitorEvent(message);
361			break;
362
363		// Font menu
364
365		case FONT_SIZE:
366		{
367			float fontSize;
368			if (message->FindFloat("size", &fontSize) == B_OK)
369				_SetFontSize(fontSize);
370			break;
371		}
372		case FONT_FAMILY:
373		{
374			const char* fontFamily = NULL;
375			const char* fontStyle = NULL;
376			void* ptr;
377			if (message->FindPointer("source", &ptr) == B_OK) {
378				BMenuItem* item = static_cast<BMenuItem*>(ptr);
379				fontFamily = item->Label();
380			}
381
382			BFont font;
383			font.SetFamilyAndStyle(fontFamily, fontStyle);
384			fItalicItem->SetMarked((font.Face() & B_ITALIC_FACE) != 0);
385			fBoldItem->SetMarked((font.Face() & B_BOLD_FACE) != 0);
386			fUnderlineItem->SetMarked((font.Face() & B_UNDERSCORE_FACE) != 0);
387
388			_SetFontStyle(fontFamily, fontStyle);
389			break;
390		}
391		case FONT_STYLE:
392		{
393			const char* fontFamily = NULL;
394			const char* fontStyle = NULL;
395			void* ptr;
396			if (message->FindPointer("source", &ptr) == B_OK) {
397				BMenuItem* item = static_cast<BMenuItem*>(ptr);
398				fontStyle = item->Label();
399				BMenu* menu = item->Menu();
400				if (menu != NULL) {
401					BMenuItem* super_item = menu->Superitem();
402					if (super_item != NULL)
403						fontFamily = super_item->Label();
404				}
405			}
406
407			BFont font;
408			font.SetFamilyAndStyle(fontFamily, fontStyle);
409			fItalicItem->SetMarked((font.Face() & B_ITALIC_FACE) != 0);
410			fBoldItem->SetMarked((font.Face() & B_BOLD_FACE) != 0);
411			fUnderlineItem->SetMarked((font.Face() & B_UNDERSCORE_FACE) != 0);
412
413			_SetFontStyle(fontFamily, fontStyle);
414			break;
415		}
416		case kMsgSetFontUp:
417		{
418			uint32 sameProperties;
419			BFont font;
420
421			fTextView->GetFontAndColor(&font, &sameProperties);
422			//GetFont seems to return a constant size for font.Size(),
423			//thus not used here (maybe that is a bug)
424			int32 cur_size = (int32)font.Size();
425
426			for (unsigned int a = 0;
427				a < sizeof(fontSizes)/sizeof(fontSizes[0]); a++) {
428				if (fontSizes[a] > cur_size) {
429					_SetFontSize(fontSizes[a]);
430					break;
431				}
432			}
433			break;
434		}
435		case kMsgSetFontDown:
436		{
437			uint32 sameProperties;
438			BFont font;
439
440			fTextView->GetFontAndColor(&font, &sameProperties);
441			int32 cur_size = (int32)font.Size();
442
443			for (unsigned int a = 1;
444				a < sizeof(fontSizes)/sizeof(fontSizes[0]); a++) {
445				if (fontSizes[a] >= cur_size) {
446					_SetFontSize(fontSizes[a-1]);
447					break;
448				}
449			}
450			break;
451		}
452		case kMsgSetItalic:
453		{
454			uint32 sameProperties;
455			BFont font;
456			fTextView->GetFontAndColor(&font, &sameProperties);
457
458			if (fItalicItem->IsMarked())
459				font.SetFace(B_REGULAR_FACE);
460			fItalicItem->SetMarked(!fItalicItem->IsMarked());
461
462			font_family family;
463			font_style style;
464			font.GetFamilyAndStyle(&family, &style);
465
466			_SetFontStyle(family, style);
467			break;
468		}
469		case kMsgSetBold:
470		{
471			uint32 sameProperties;
472			BFont font;
473			fTextView->GetFontAndColor(&font, &sameProperties);
474
475			if (fBoldItem->IsMarked())
476				font.SetFace(B_REGULAR_FACE);
477			fBoldItem->SetMarked(!fBoldItem->IsMarked());
478
479			font_family family;
480			font_style style;
481			font.GetFamilyAndStyle(&family, &style);
482
483			_SetFontStyle(family, style);
484			break;
485		}
486		case kMsgSetUnderline:
487		{
488			uint32 sameProperties;
489			BFont font;
490			fTextView->GetFontAndColor(&font, &sameProperties);
491
492			if (fUnderlineItem->IsMarked())
493				font.SetFace(B_REGULAR_FACE);
494			fUnderlineItem->SetMarked(!fUnderlineItem->IsMarked());
495
496			font_family family;
497			font_style style;
498			font.GetFamilyAndStyle(&family, &style);
499
500			_SetFontStyle(family, style);
501			break;
502		}
503		case FONT_COLOR:
504		{
505			ssize_t colorLength;
506			rgb_color* color;
507			if (message->FindData("color", B_RGB_COLOR_TYPE,
508					(const void**)&color, &colorLength) == B_OK
509				&& colorLength == sizeof(rgb_color)) {
510				/*
511				 * TODO: Ideally, when selecting the default color,
512				 * you wouldn't naively apply it; it shouldn't lose its nature.
513				 * When reloaded with a different default color, it should
514				 * reflect that different choice.
515				 */
516				_SetFontColor(color);
517			}
518			break;
519		}
520
521		// Document menu
522
523		case ALIGN_LEFT:
524			fTextView->SetAlignment(B_ALIGN_LEFT);
525			_UpdateCleanUndoRedoSaveRevert();
526			break;
527		case ALIGN_CENTER:
528			fTextView->SetAlignment(B_ALIGN_CENTER);
529			_UpdateCleanUndoRedoSaveRevert();
530			break;
531		case ALIGN_RIGHT:
532			fTextView->SetAlignment(B_ALIGN_RIGHT);
533			_UpdateCleanUndoRedoSaveRevert();
534			break;
535		case WRAP_LINES:
536		{
537			// update wrap setting
538			if (fTextView->DoesWordWrap()) {
539				fTextView->SetWordWrap(false);
540				fWrapItem->SetMarked(false);
541			} else {
542				fTextView->SetWordWrap(true);
543				fWrapItem->SetMarked(true);
544			}
545
546			// update buttons
547			_UpdateCleanUndoRedoSaveRevert();
548			break;
549		}
550		case SHOW_STATISTICS:
551			_ShowStatistics();
552			break;
553		case ENABLE_ITEMS:
554			fCutItem->SetEnabled(true);
555			fCopyItem->SetEnabled(true);
556			break;
557		case DISABLE_ITEMS:
558			fCutItem->SetEnabled(false);
559			fCopyItem->SetEnabled(false);
560			break;
561		case TEXT_CHANGED:
562			if (fUndoFlag) {
563				if (fUndoCleans) {
564					// we cleaned!
565					fClean = true;
566					fUndoCleans = false;
567				} else if (fClean) {
568					// if we were clean
569					// then a redo will make us clean again
570					fRedoCleans = true;
571					fClean = false;
572				}
573				// set mode
574				fCanUndo = false;
575				fCanRedo = true;
576				fUndoItem->SetLabel(B_TRANSLATE("Redo typing"));
577				fUndoItem->SetEnabled(true);
578				fUndoFlag = false;
579			} else {
580				if (fRedoFlag && fRedoCleans) {
581					// we cleaned!
582					fClean = true;
583					fRedoCleans = false;
584				} else if (fClean) {
585					// if we were clean
586					// then an undo will make us clean again
587					fUndoCleans = true;
588					fClean = false;
589				} else {
590					// no more cleaning from undo now...
591					fUndoCleans = false;
592				}
593				// set mode
594				fCanUndo = true;
595				fCanRedo = false;
596				fUndoItem->SetLabel(B_TRANSLATE("Undo typing"));
597				fUndoItem->SetEnabled(true);
598				fRedoFlag = false;
599			}
600			if (fClean) {
601				fSaveItem->SetEnabled(fSaveMessage == NULL);
602			} else {
603				fSaveItem->SetEnabled(true);
604			}
605			fReloadItem->SetEnabled(fSaveMessage != NULL);
606			fEncodingItem->SetEnabled(fSaveMessage != NULL);
607			break;
608
609		case SAVE_AS_ENCODING:
610			void* ptr;
611			if (message->FindPointer("source", &ptr) == B_OK
612				&& fSavePanelEncodingMenu != NULL) {
613				fTextView->SetEncoding(
614					(uint32)fSavePanelEncodingMenu->IndexOf((BMenuItem*)ptr));
615			}
616			break;
617
618		case UPDATE_STATUS:
619		{
620			message->AddBool("modified", !fClean);
621			bool readOnly = !fTextView->IsEditable();
622			message->AddBool("readOnly", readOnly);
623			if (readOnly) {
624				BVolume volume(fNodeRef.device);
625				message->AddBool("canUnlock", !volume.IsReadOnly());
626			}
627			fStatusView->SetStatus(message);
628			break;
629		}
630
631		case UPDATE_STATUS_REF:
632		{
633			entry_ref ref;
634			const char* name;
635
636			if (fSaveMessage != NULL
637				&& fSaveMessage->FindRef("directory", &ref) == B_OK
638				&& fSaveMessage->FindString("name", &name) == B_OK) {
639
640				BDirectory dir(&ref);
641				status_t status = dir.InitCheck();
642				BEntry entry;
643				if (status == B_OK)
644					status = entry.SetTo(&dir, name);
645				if (status == B_OK)
646					status = entry.GetRef(&ref);
647			}
648			fStatusView->SetRef(ref);
649			break;
650		}
651
652		case UNLOCK_FILE:
653		{
654			status_t status = _UnlockFile();
655			if (status != B_OK) {
656				BString text;
657				bs_printf(&text,
658					B_TRANSLATE("Unable to unlock file\n\t%s"),
659					strerror(status));
660				_ShowAlert(text, B_TRANSLATE("OK"), "", "", B_STOP_ALERT);
661			}
662			PostMessage(UPDATE_STATUS);
663			break;
664		}
665
666		case UPDATE_LINE_SELECTION:
667		{
668			int32 line;
669			if (message->FindInt32("be:line", &line) == B_OK) {
670				fTextView->GoToLine(line);
671				fTextView->ScrollToSelection();
672			}
673
674			int32 start, length;
675			if (message->FindInt32("be:selection_offset", &start) == B_OK) {
676				if (message->FindInt32("be:selection_length", &length) != B_OK)
677					length = 0;
678
679				fTextView->Select(start, start + length);
680				fTextView->ScrollToOffset(start);
681			}
682			break;
683		}
684		default:
685			BWindow::MessageReceived(message);
686			break;
687	}
688}
689
690
691void
692StyledEditWindow::MenusBeginning()
693{
694	// update the font menu
695	// unselect the old values
696	if (fCurrentFontItem != NULL) {
697		fCurrentFontItem->SetMarked(false);
698		BMenu* menu = fCurrentFontItem->Submenu();
699		if (menu != NULL) {
700			BMenuItem* item = menu->FindMarked();
701			if (item != NULL)
702				item->SetMarked(false);
703		}
704	}
705
706	if (fCurrentStyleItem != NULL) {
707		fCurrentStyleItem->SetMarked(false);
708	}
709
710	BMenuItem* oldColorItem = fFontColorMenu->FindMarked();
711	if (oldColorItem != NULL)
712		oldColorItem->SetMarked(false);
713
714	BMenuItem* oldSizeItem = fFontSizeMenu->FindMarked();
715	if (oldSizeItem != NULL)
716		oldSizeItem->SetMarked(false);
717
718	// find the current font, color, size
719	BFont font;
720	uint32 sameProperties;
721	rgb_color color = ui_color(B_DOCUMENT_TEXT_COLOR);
722	bool sameColor;
723	fTextView->GetFontAndColor(&font, &sameProperties, &color, &sameColor);
724	color.alpha = 255;
725
726	if (sameColor) {
727		if (fDefaultFontColorItem->Color() == color)
728			fDefaultFontColorItem->SetMarked(true);
729		else {
730			for (int i = 0; i < fFontColorMenu->CountItems(); i++) {
731				ColorMenuItem* item = dynamic_cast<ColorMenuItem*>
732					(fFontColorMenu->ItemAt(i));
733				if (item != NULL && item->Color() == color) {
734					item->SetMarked(true);
735					break;
736				}
737			}
738		}
739	}
740
741	if (sameProperties & B_FONT_SIZE) {
742		if ((int)font.Size() == font.Size()) {
743			// select the current font size
744			char fontSizeStr[16];
745			snprintf(fontSizeStr, 15, "%i", (int)font.Size());
746			BMenuItem* item = fFontSizeMenu->FindItem(fontSizeStr);
747			if (item != NULL)
748				item->SetMarked(true);
749		}
750	}
751
752	font_family family;
753	font_style style;
754	font.GetFamilyAndStyle(&family, &style);
755
756	fCurrentFontItem = fFontMenu->FindItem(family);
757
758	if (fCurrentFontItem != NULL) {
759		fCurrentFontItem->SetMarked(true);
760		BMenu* menu = fCurrentFontItem->Submenu();
761		if (menu != NULL) {
762			BMenuItem* item = menu->FindItem(style);
763			fCurrentStyleItem = item;
764			if (fCurrentStyleItem != NULL)
765				item->SetMarked(true);
766		}
767	}
768
769	fBoldItem->SetMarked((font.Face() & B_BOLD_FACE) != 0);
770	fItalicItem->SetMarked((font.Face() & B_ITALIC_FACE) != 0);
771	fUnderlineItem->SetMarked((font.Face() & B_UNDERSCORE_FACE) != 0);
772
773	switch (fTextView->Alignment()) {
774		case B_ALIGN_LEFT:
775		default:
776			fAlignLeft->SetMarked(true);
777			break;
778		case B_ALIGN_CENTER:
779			fAlignCenter->SetMarked(true);
780			break;
781		case B_ALIGN_RIGHT:
782			fAlignRight->SetMarked(true);
783			break;
784	}
785
786	// text encoding
787	const BCharacterSet* charset
788		= BCharacterSetRoster::GetCharacterSetByFontID(fTextView->GetEncoding());
789	BMenu* encodingMenu = fEncodingItem->Submenu();
790	if (charset != NULL && encodingMenu != NULL) {
791		const char* mime = charset->GetMIMEName();
792		BString name(charset->GetPrintName());
793		if (mime)
794			name << " (" << mime << ")";
795
796		BMenuItem* item = encodingMenu->FindItem(name);
797		if (item != NULL)
798			item->SetMarked(true);
799	}
800}
801
802
803#undef B_TRANSLATION_CONTEXT
804#define B_TRANSLATION_CONTEXT "SaveAlert"
805
806
807status_t
808StyledEditWindow::Save(BMessage* message)
809{
810	_NodeMonitorSuspender nodeMonitorSuspender(this);
811
812	if (!message)
813		message = fSaveMessage;
814
815	if (!message)
816		return B_ERROR;
817
818	entry_ref dirRef;
819	const char* name;
820	if (message->FindRef("directory", &dirRef) != B_OK
821		|| message->FindString("name", &name) != B_OK)
822		return B_BAD_VALUE;
823
824	BDirectory dir(&dirRef);
825	BEntry entry(&dir, name);
826
827	status_t status = B_ERROR;
828	if (dir.InitCheck() == B_OK && entry.InitCheck() == B_OK) {
829		struct stat st;
830		BFile file(&entry, B_READ_WRITE | B_CREATE_FILE);
831		if (file.InitCheck() == B_OK
832			&& (status = file.GetStat(&st)) == B_OK) {
833			// check the file permissions
834			if (!((getuid() == st.st_uid && (S_IWUSR & st.st_mode))
835				|| (getgid() == st.st_gid && (S_IWGRP & st.st_mode))
836				|| (S_IWOTH & st.st_mode))) {
837				BString alertText;
838				bs_printf(&alertText, B_TRANSLATE("This file is marked "
839					"read-only. Save changes to the document \"%s\"? "), name);
840				switch (_ShowAlert(alertText, B_TRANSLATE("Cancel"),
841						B_TRANSLATE("Don't save"),
842						B_TRANSLATE("Save"), B_WARNING_ALERT)) {
843					case 0:
844						return B_CANCELED;
845					case 1:
846						return B_OK;
847					default:
848						break;
849				}
850			}
851
852			status = fTextView->WriteStyledEditFile(&file);
853		}
854	}
855
856	if (status != B_OK) {
857		BString alertText;
858		bs_printf(&alertText, B_TRANSLATE("Error saving \"%s\":\n%s"), name,
859			strerror(status));
860
861		_ShowAlert(alertText, B_TRANSLATE("OK"), "", "", B_STOP_ALERT);
862		return status;
863	}
864
865	SetTitle(name);
866
867	if (fSaveMessage != message) {
868		delete fSaveMessage;
869		fSaveMessage = new BMessage(*message);
870	}
871
872	// clear clean modes
873	fSaveItem->SetEnabled(false);
874	fUndoCleans = false;
875	fRedoCleans = false;
876	fClean = true;
877	fNagOnNodeChange = true;
878
879	PostMessage(UPDATE_STATUS);
880	PostMessage(UPDATE_STATUS_REF);
881
882	return status;
883}
884
885
886#undef B_TRANSLATION_CONTEXT
887#define B_TRANSLATION_CONTEXT "Open_and_SaveAsPanel"
888
889
890status_t
891StyledEditWindow::SaveAs(BMessage* message)
892{
893	if (fSavePanel == NULL) {
894		entry_ref* directory = NULL;
895		entry_ref dirRef;
896		if (fSaveMessage != NULL) {
897			if (fSaveMessage->FindRef("directory", &dirRef) == B_OK)
898				directory = &dirRef;
899		}
900
901		BMessenger target(this);
902		fSavePanel = new BFilePanel(B_SAVE_PANEL, &target,
903			directory, B_FILE_NODE, false);
904
905		BMenuBar* menuBar = dynamic_cast<BMenuBar*>(
906			fSavePanel->Window()->FindView("MenuBar"));
907		if (menuBar != NULL) {
908			fSavePanelEncodingMenu = new BMenu(B_TRANSLATE("Encoding"));
909			fSavePanelEncodingMenu->SetRadioMode(true);
910			menuBar->AddItem(fSavePanelEncodingMenu);
911
912			BCharacterSetRoster roster;
913			BCharacterSet charset;
914			while (roster.GetNextCharacterSet(&charset) == B_NO_ERROR) {
915				BString name(charset.GetPrintName());
916				const char* mime = charset.GetMIMEName();
917				if (mime) {
918					name.Append(" (");
919					name.Append(mime);
920					name.Append(")");
921				}
922				BMenuItem * item = new BMenuItem(name.String(),
923					new BMessage(SAVE_AS_ENCODING));
924				item->SetTarget(this);
925				fSavePanelEncodingMenu->AddItem(item);
926				if (charset.GetFontID() == fTextView->GetEncoding())
927					item->SetMarked(true);
928			}
929		}
930	}
931
932	fSavePanel->SetSaveText(Title());
933	if (message != NULL)
934		fSavePanel->SetMessage(message);
935
936	fSavePanel->Show();
937	return B_OK;
938}
939
940
941void
942StyledEditWindow::OpenFile(entry_ref* ref)
943{
944	if (_LoadFile(ref) != B_OK) {
945		fSaveItem->SetEnabled(true);
946			// allow saving new files
947		return;
948	}
949
950	fSaveMessage = new(std::nothrow) BMessage(B_SAVE_REQUESTED);
951	if (fSaveMessage) {
952		BEntry entry(ref, true);
953		BEntry parent;
954		entry_ref parentRef;
955		char name[B_FILE_NAME_LENGTH];
956
957		entry.GetParent(&parent);
958		entry.GetName(name);
959		parent.GetRef(&parentRef);
960		fSaveMessage->AddRef("directory", &parentRef);
961		fSaveMessage->AddString("name", name);
962		SetTitle(name);
963
964		_LoadAttrs();
965	}
966
967	_SwitchNodeMonitor(true, ref);
968
969	PostMessage(UPDATE_STATUS_REF);
970
971	fReloadItem->SetEnabled(fSaveMessage != NULL);
972	fEncodingItem->SetEnabled(fSaveMessage != NULL);
973}
974
975
976status_t
977StyledEditWindow::PageSetup(const char* documentName)
978{
979	BPrintJob printJob(documentName);
980
981	if (fPrintSettings != NULL)
982		printJob.SetSettings(new BMessage(*fPrintSettings));
983
984	status_t result = printJob.ConfigPage();
985	if (result == B_OK) {
986		delete fPrintSettings;
987		fPrintSettings = printJob.Settings();
988	}
989
990	return result;
991}
992
993
994void
995StyledEditWindow::Print(const char* documentName)
996{
997	BPrintJob printJob(documentName);
998	if (fPrintSettings)
999		printJob.SetSettings(new BMessage(*fPrintSettings));
1000
1001	if (printJob.ConfigJob() != B_OK)
1002		return;
1003
1004	delete fPrintSettings;
1005	fPrintSettings = printJob.Settings();
1006
1007	// information from printJob
1008	BRect printableRect = printJob.PrintableRect();
1009	int32 firstPage = printJob.FirstPage();
1010	int32 lastPage = printJob.LastPage();
1011
1012	// lines eventually to be used to compute pages to print
1013	int32 firstLine = 0;
1014	int32 lastLine = fTextView->CountLines();
1015
1016	// values to be computed
1017	int32 pagesInDocument = 1;
1018	int32 linesInDocument = fTextView->CountLines();
1019
1020	int32 currentLine = 0;
1021	while (currentLine < linesInDocument) {
1022		float currentHeight = 0;
1023		while (currentHeight < printableRect.Height() && currentLine
1024				< linesInDocument) {
1025			currentHeight += fTextView->LineHeight(currentLine);
1026			if (currentHeight < printableRect.Height())
1027				currentLine++;
1028		}
1029		if (pagesInDocument == lastPage)
1030			lastLine = currentLine - 1;
1031
1032		if (currentHeight >= printableRect.Height()) {
1033			pagesInDocument++;
1034			if (pagesInDocument == firstPage)
1035				firstLine = currentLine;
1036		}
1037	}
1038
1039	if (lastPage > pagesInDocument - 1) {
1040		lastPage = pagesInDocument - 1;
1041		lastLine = currentLine - 1;
1042	}
1043
1044
1045	printJob.BeginJob();
1046	if (fTextView->CountLines() > 0 && fTextView->TextLength() > 0) {
1047		int32 printLine = firstLine;
1048		while (printLine <= lastLine) {
1049			float currentHeight = 0;
1050			int32 firstLineOnPage = printLine;
1051			while (currentHeight < printableRect.Height()
1052				&& printLine <= lastLine)
1053			{
1054				currentHeight += fTextView->LineHeight(printLine);
1055				if (currentHeight < printableRect.Height())
1056					printLine++;
1057			}
1058
1059			float top = 0;
1060			if (firstLineOnPage != 0)
1061				top = fTextView->TextHeight(0, firstLineOnPage - 1);
1062
1063			float bottom = fTextView->TextHeight(0, printLine - 1);
1064			BRect textRect(0.0, top + TEXT_INSET,
1065				printableRect.Width(), bottom + TEXT_INSET);
1066			printJob.DrawView(fTextView, textRect, B_ORIGIN);
1067			printJob.SpoolPage();
1068		}
1069	}
1070
1071
1072	printJob.CommitJob();
1073}
1074
1075
1076void
1077StyledEditWindow::SearchAllWindows(BString find, BString replace,
1078	bool caseSensitive)
1079{
1080	int32 numWindows;
1081	numWindows = be_app->CountWindows();
1082
1083	BMessage* message;
1084	message= new BMessage(MSG_REPLACE_ALL);
1085	message->AddString("FindText", find);
1086	message->AddString("ReplaceText", replace);
1087	message->AddBool("casesens", caseSensitive);
1088
1089	while (numWindows >= 0) {
1090		StyledEditWindow* window = dynamic_cast<StyledEditWindow *>(
1091			be_app->WindowAt(numWindows));
1092
1093		BMessenger messenger(window);
1094		messenger.SendMessage(message);
1095
1096		numWindows--;
1097	}
1098}
1099
1100
1101bool
1102StyledEditWindow::IsDocumentEntryRef(const entry_ref* ref)
1103{
1104	if (ref == NULL)
1105		return false;
1106
1107	if (fSaveMessage == NULL)
1108		return false;
1109
1110	entry_ref dir;
1111	const char* name;
1112	if (fSaveMessage->FindRef("directory", &dir) != B_OK
1113		|| fSaveMessage->FindString("name", &name) != B_OK)
1114		return false;
1115
1116	entry_ref documentRef;
1117	BPath documentPath(&dir);
1118	documentPath.Append(name);
1119	get_ref_for_path(documentPath.Path(), &documentRef);
1120
1121	return *ref == documentRef;
1122}
1123
1124
1125// #pragma mark - private methods
1126
1127
1128#undef B_TRANSLATION_CONTEXT
1129#define B_TRANSLATION_CONTEXT "Menus"
1130
1131
1132void
1133StyledEditWindow::_InitWindow(uint32 encoding)
1134{
1135	fPrintSettings = NULL;
1136	fSaveMessage = NULL;
1137
1138	// undo modes
1139	fUndoFlag = false;
1140	fCanUndo = false;
1141	fRedoFlag = false;
1142	fCanRedo = false;
1143
1144	// clean modes
1145	fUndoCleans = false;
1146	fRedoCleans = false;
1147	fClean = true;
1148
1149	// search- state
1150	fReplaceString = "";
1151	fStringToFind = "";
1152	fCaseSensitive = false;
1153	fWrapAround = false;
1154	fBackSearch = false;
1155
1156	fNagOnNodeChange = true;
1157
1158
1159	// add textview and scrollview
1160
1161	BRect viewFrame = Bounds();
1162	BRect textBounds = viewFrame;
1163	textBounds.OffsetTo(B_ORIGIN);
1164
1165	fTextView = new StyledEditView(viewFrame, textBounds, this);
1166	fTextView->SetInsets(TEXT_INSET, TEXT_INSET, TEXT_INSET, TEXT_INSET);
1167	fTextView->SetDoesUndo(true);
1168	fTextView->SetStylable(true);
1169	fTextView->SetEncoding(encoding);
1170
1171	fScrollView = new BScrollView("scrollview", fTextView, B_FOLLOW_ALL, 0,
1172		true, true, B_PLAIN_BORDER);
1173
1174	fStatusView = new StatusView(fScrollView);
1175	fScrollView->AddChild(fStatusView);
1176
1177	BMenuItem* openItem = new BMenuItem(BRecentFilesList::NewFileListMenu(
1178		B_TRANSLATE("Open" B_UTF8_ELLIPSIS), NULL, NULL, be_app, 9, true,
1179		NULL, APP_SIGNATURE), new BMessage(MENU_OPEN));
1180	openItem->SetShortcut('O', 0);
1181	openItem->SetTarget(be_app);
1182
1183	fSaveItem = new BMenuItem(B_TRANSLATE("Save"),new BMessage(MENU_SAVE), 'S');
1184	fSaveItem->SetEnabled(false);
1185
1186	fReloadItem = new BMenuItem(B_TRANSLATE("Reload" B_UTF8_ELLIPSIS),
1187	new BMessage(MENU_RELOAD), 'L');
1188	fReloadItem->SetEnabled(false);
1189
1190	fUndoItem = new BMenuItem(B_TRANSLATE("Can't undo"),
1191		new BMessage(B_UNDO), 'Z');
1192	fUndoItem->SetEnabled(false);
1193
1194	fCutItem = new BMenuItem(B_TRANSLATE("Cut"),
1195		new BMessage(B_CUT), 'X');
1196	fCutItem->SetEnabled(false);
1197
1198	fCopyItem = new BMenuItem(B_TRANSLATE("Copy"),
1199		new BMessage(B_COPY), 'C');
1200	fCopyItem->SetEnabled(false);
1201
1202	fFindAgainItem = new BMenuItem(B_TRANSLATE("Find again"),
1203		new BMessage(MENU_FIND_AGAIN), 'G');
1204	fFindAgainItem->SetEnabled(false);
1205
1206	fReplaceItem = new BMenuItem(B_TRANSLATE("Replace" B_UTF8_ELLIPSIS),
1207		new BMessage(MENU_REPLACE), 'R');
1208
1209	fReplaceSameItem = new BMenuItem(B_TRANSLATE("Replace next"),
1210		new BMessage(MENU_REPLACE_SAME), 'T');
1211	fReplaceSameItem->SetEnabled(false);
1212
1213	fFontSizeMenu = new BMenu(B_TRANSLATE("Size"));
1214	fFontSizeMenu->SetRadioMode(true);
1215
1216	BMenuItem* menuItem;
1217	for (uint32 i = 0; i < sizeof(fontSizes) / sizeof(fontSizes[0]); i++) {
1218		BMessage* fontMessage = new BMessage(FONT_SIZE);
1219		fontMessage->AddFloat("size", fontSizes[i]);
1220
1221		char label[64];
1222		snprintf(label, sizeof(label), "%" B_PRId32, fontSizes[i]);
1223		fFontSizeMenu->AddItem(menuItem = new BMenuItem(label, fontMessage));
1224
1225		if (fontSizes[i] == (int32)be_plain_font->Size())
1226			menuItem->SetMarked(true);
1227	}
1228
1229	fFontColorMenu = new BMenu(B_TRANSLATE("Color"), 0, 0);
1230	fFontColorMenu->SetRadioMode(true);
1231
1232	_BuildFontColorMenu(fFontColorMenu);
1233
1234	fBoldItem = new BMenuItem(B_TRANSLATE("Bold"),
1235		new BMessage(kMsgSetBold));
1236	fBoldItem->SetShortcut('B', 0);
1237
1238	fItalicItem = new BMenuItem(B_TRANSLATE("Italic"),
1239		new BMessage(kMsgSetItalic));
1240	fItalicItem->SetShortcut('I', 0);
1241
1242	fUnderlineItem = new BMenuItem(B_TRANSLATE("Underline"),
1243		new BMessage(kMsgSetUnderline));
1244	fUnderlineItem->SetShortcut('U', 0);
1245
1246	fFontMenu = new BMenu(B_TRANSLATE("Font"));
1247	fCurrentFontItem = 0;
1248	fCurrentStyleItem = 0;
1249
1250	// premake font menu since we cant add members dynamically later
1251	BLayoutBuilder::Menu<>(fFontMenu)
1252		.AddItem(fFontSizeMenu)
1253		.AddItem(fFontColorMenu)
1254		.AddSeparator()
1255		.AddItem(B_TRANSLATE("Increase size"), kMsgSetFontUp, '+')
1256		.AddItem(B_TRANSLATE("Decrease size"), kMsgSetFontDown, '-')
1257		.AddItem(fBoldItem)
1258		.AddItem(fItalicItem)
1259		.AddItem(fUnderlineItem)
1260		.AddSeparator()
1261	.End();
1262
1263	BMenu* subMenu;
1264	int32 numFamilies = count_font_families();
1265	for (int32 i = 0; i < numFamilies; i++) {
1266		font_family family;
1267		if (get_font_family(i, &family) == B_OK) {
1268			subMenu = new BMenu(family);
1269			subMenu->SetRadioMode(true);
1270			fFontMenu->AddItem(new BMenuItem(subMenu,
1271				new BMessage(FONT_FAMILY)));
1272
1273			int32 numStyles = count_font_styles(family);
1274			for (int32 j = 0; j < numStyles; j++) {
1275				font_style style;
1276				uint32 flags;
1277				if (get_font_style(family, j, &style, &flags) == B_OK) {
1278					subMenu->AddItem(new BMenuItem(style,
1279						new BMessage(FONT_STYLE)));
1280				}
1281			}
1282		}
1283	}
1284
1285	// "Align"-subMenu:
1286	BMenu* alignMenu = new BMenu(B_TRANSLATE("Align"));
1287	alignMenu->SetRadioMode(true);
1288
1289	alignMenu->AddItem(fAlignLeft = new BMenuItem(B_TRANSLATE("Left"),
1290		new BMessage(ALIGN_LEFT)));
1291	fAlignLeft->SetMarked(true);
1292	fAlignLeft->SetShortcut('L', B_OPTION_KEY);
1293
1294	alignMenu->AddItem(fAlignCenter = new BMenuItem(B_TRANSLATE("Center"),
1295		new BMessage(ALIGN_CENTER)));
1296	fAlignCenter->SetShortcut('C', B_OPTION_KEY);
1297
1298	alignMenu->AddItem(fAlignRight = new BMenuItem(B_TRANSLATE("Right"),
1299		new BMessage(ALIGN_RIGHT)));
1300	fAlignRight->SetShortcut('R', B_OPTION_KEY);
1301
1302	fWrapItem = new BMenuItem(B_TRANSLATE("Wrap lines"),
1303		new BMessage(WRAP_LINES));
1304	fWrapItem->SetMarked(true);
1305	fWrapItem->SetShortcut('W', B_OPTION_KEY);
1306
1307	BMessage *message = new BMessage(MENU_RELOAD);
1308	message->AddString("encoding", "auto");
1309	fEncodingItem = new BMenuItem(_PopulateEncodingMenu(
1310		new BMenu(B_TRANSLATE("Text encoding")), "UTF-8"),
1311		message);
1312	fEncodingItem->SetEnabled(false);
1313
1314	BMenuBar* mainMenu = new BMenuBar("mainMenu");
1315
1316	BLayoutBuilder::Menu<>(mainMenu)
1317		.AddMenu(B_TRANSLATE("File"))
1318			.AddItem(B_TRANSLATE("New"), MENU_NEW, 'N')
1319			.AddItem(openItem)
1320			.AddSeparator()
1321			.AddItem(fSaveItem)
1322			.AddItem(B_TRANSLATE("Save as" B_UTF8_ELLIPSIS),
1323				MENU_SAVEAS, 'S', B_SHIFT_KEY)
1324			.AddItem(fReloadItem)
1325			.AddItem(B_TRANSLATE("Close"), MENU_CLOSE, 'W')
1326			.AddSeparator()
1327			.AddItem(B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS), MENU_PAGESETUP)
1328			.AddItem(B_TRANSLATE("Print" B_UTF8_ELLIPSIS), MENU_PRINT, 'P')
1329			.AddSeparator()
1330			.AddItem(B_TRANSLATE("Quit"), MENU_QUIT, 'Q')
1331		.End()
1332		.AddMenu(B_TRANSLATE("Edit"))
1333			.AddItem(fUndoItem)
1334			.AddSeparator()
1335			.AddItem(fCutItem)
1336			.AddItem(fCopyItem)
1337			.AddItem(B_TRANSLATE("Paste"), B_PASTE, 'V')
1338			.AddSeparator()
1339			.AddItem(B_TRANSLATE("Select all"), B_SELECT_ALL, 'A')
1340			.AddSeparator()
1341			.AddItem(B_TRANSLATE("Find" B_UTF8_ELLIPSIS), MENU_FIND, 'F')
1342			.AddItem(fFindAgainItem)
1343			.AddItem(B_TRANSLATE("Find selection"), MENU_FIND_SELECTION, 'H')
1344			.AddItem(fReplaceItem)
1345			.AddItem(fReplaceSameItem)
1346		.End()
1347		.AddItem(fFontMenu)
1348		.AddMenu(B_TRANSLATE("Document"))
1349			.AddItem(alignMenu)
1350			.AddItem(fWrapItem)
1351			.AddItem(fEncodingItem)
1352			.AddSeparator()
1353			.AddItem(B_TRANSLATE("Statistics" B_UTF8_ELLIPSIS), SHOW_STATISTICS)
1354		.End();
1355
1356
1357	fSavePanel = NULL;
1358	fSavePanelEncodingMenu = NULL;
1359
1360	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
1361		.Add(mainMenu)
1362		.AddGroup(B_VERTICAL, 0)
1363			.SetInsets(-1)
1364			.Add(fScrollView)
1365		.End()
1366	.End();
1367
1368	SetKeyMenuBar(mainMenu);
1369
1370	// must focus text view after window layout is built
1371	fTextView->MakeFocus(true);
1372}
1373
1374
1375void
1376StyledEditWindow::_BuildFontColorMenu(BMenu* menu)
1377{
1378	if (menu == NULL)
1379		return;
1380
1381	BFont font;
1382	menu->GetFont(&font);
1383	font_height fh;
1384	font.GetHeight(&fh);
1385
1386	const float itemHeight = ceilf(fh.ascent + fh.descent + 2 * fh.leading);
1387	const float margin = 8.0;
1388	const int nbColumns = 5;
1389
1390	BMessage msgTemplate(FONT_COLOR);
1391	BRect matrixArea(0, 0, 0, 0);
1392
1393	// we place the color palette, reserving room at the top
1394	for (uint i = 0; i < sizeof(palette) / sizeof(rgb_color); i++) {
1395		BPoint topLeft((i % nbColumns) * (itemHeight + margin),
1396			(i / nbColumns) * (itemHeight + margin));
1397		BRect buttonArea(topLeft.x, topLeft.y, topLeft.x + itemHeight,
1398			topLeft.y + itemHeight);
1399		buttonArea.OffsetBy(margin, itemHeight + margin + margin);
1400		menu->AddItem(
1401			new ColorMenuItem("", palette[i], new BMessage(msgTemplate)),
1402			buttonArea);
1403		buttonArea.OffsetBy(margin, margin);
1404		matrixArea = matrixArea | buttonArea;
1405	}
1406
1407	// separator at the bottom to add spacing in the matrix menu
1408	matrixArea.top = matrixArea.bottom;
1409	menu->AddItem(new BSeparatorItem(), matrixArea);
1410
1411	matrixArea.top = 0;
1412	matrixArea.bottom = itemHeight + 4;
1413
1414	BMessage* msg = new BMessage(msgTemplate);
1415	msg->AddBool("default", true);
1416	fDefaultFontColorItem = new ColorMenuItem(B_TRANSLATE("Default"),
1417		ui_color(B_DOCUMENT_TEXT_COLOR), msg);
1418	menu->AddItem(fDefaultFontColorItem, matrixArea);
1419
1420	matrixArea.top = matrixArea.bottom;
1421	matrixArea.bottom = matrixArea.top + margin;
1422	menu->AddItem(new BSeparatorItem(), matrixArea);
1423}
1424
1425
1426void
1427StyledEditWindow::_LoadAttrs()
1428{
1429	entry_ref dir;
1430	const char* name;
1431	if (fSaveMessage->FindRef("directory", &dir) != B_OK
1432		|| fSaveMessage->FindString("name", &name) != B_OK)
1433		return;
1434
1435	BPath documentPath(&dir);
1436	documentPath.Append(name);
1437
1438	BNode documentNode(documentPath.Path());
1439	if (documentNode.InitCheck() != B_OK)
1440		return;
1441
1442	// info about position of caret may live in the file attributes
1443	int32 position = 0;
1444	if (documentNode.ReadAttr("be:caret_position", B_INT32_TYPE, 0,
1445			&position, sizeof(position)) != sizeof(position))
1446		position = 0;
1447
1448	fTextView->Select(position, position);
1449	fTextView->ScrollToOffset(position);
1450
1451	BRect newFrame;
1452	ssize_t bytesRead = documentNode.ReadAttr(kInfoAttributeName, B_RECT_TYPE,
1453		0, &newFrame, sizeof(BRect));
1454	if (bytesRead != sizeof(BRect))
1455		return;
1456
1457	swap_data(B_RECT_TYPE, &newFrame, sizeof(BRect), B_SWAP_BENDIAN_TO_HOST);
1458
1459	// Check if the frame in on screen, otherwise, ignore it
1460	BScreen screen(this);
1461	if (newFrame.Width() > 32 && newFrame.Height() > 32
1462		&& screen.Frame().Contains(newFrame)) {
1463		MoveTo(newFrame.left, newFrame.top);
1464		ResizeTo(newFrame.Width(), newFrame.Height());
1465	}
1466}
1467
1468
1469void
1470StyledEditWindow::_SaveAttrs()
1471{
1472	if (!fSaveMessage)
1473		return;
1474
1475	entry_ref dir;
1476	const char* name;
1477	if (fSaveMessage->FindRef("directory", &dir) != B_OK
1478		|| fSaveMessage->FindString("name", &name) != B_OK)
1479		return;
1480
1481	BPath documentPath(&dir);
1482	documentPath.Append(name);
1483
1484	BNode documentNode(documentPath.Path());
1485	if (documentNode.InitCheck() != B_OK)
1486		return;
1487
1488	BRect frame(Frame());
1489	swap_data(B_RECT_TYPE, &frame, sizeof(BRect), B_SWAP_HOST_TO_BENDIAN);
1490
1491	documentNode.WriteAttr(kInfoAttributeName, B_RECT_TYPE, 0, &frame,
1492		sizeof(BRect));
1493
1494	// preserve caret line and position
1495	int32 start, end;
1496	fTextView->GetSelection(&start, &end);
1497	documentNode.WriteAttr("be:caret_position",
1498			B_INT32_TYPE, 0, &start, sizeof(start));
1499}
1500
1501
1502#undef B_TRANSLATION_CONTEXT
1503#define B_TRANSLATION_CONTEXT "LoadAlert"
1504
1505
1506status_t
1507StyledEditWindow::_LoadFile(entry_ref* ref, const char* forceEncoding)
1508{
1509	BEntry entry(ref, true);
1510		// traverse an eventual link
1511
1512	status_t status = entry.InitCheck();
1513	if (status == B_OK && entry.IsDirectory())
1514		status = B_IS_A_DIRECTORY;
1515
1516	BFile file;
1517	if (status == B_OK)
1518		status = file.SetTo(&entry, B_READ_ONLY);
1519	if (status == B_OK)
1520		status = fTextView->GetStyledText(&file, forceEncoding);
1521
1522	if (status == B_ENTRY_NOT_FOUND) {
1523		// Treat non-existing files consideratley; we just want to get an
1524		// empty window for them - to create this new document
1525		status = B_OK;
1526	}
1527
1528	if (status != B_OK) {
1529		// If an error occured, bail out and tell the user what happened
1530		BEntry entry(ref, true);
1531		char name[B_FILE_NAME_LENGTH];
1532		if (entry.GetName(name) != B_OK)
1533			strlcpy(name, B_TRANSLATE("???"), sizeof(name));
1534
1535		BString text;
1536		if (status == B_BAD_TYPE)
1537			bs_printf(&text,
1538				B_TRANSLATE("Error loading \"%s\":\n\tUnsupported format"), name);
1539		else
1540			bs_printf(&text, B_TRANSLATE("Error loading \"%s\":\n\t%s"),
1541				name, strerror(status));
1542
1543		_ShowAlert(text, B_TRANSLATE("OK"), "", "", B_STOP_ALERT);
1544		return status;
1545	}
1546
1547	struct stat st;
1548	if (file.InitCheck() == B_OK && file.GetStat(&st) == B_OK) {
1549		bool editable = (getuid() == st.st_uid && S_IWUSR & st.st_mode)
1550					|| (getgid() == st.st_gid && S_IWGRP & st.st_mode)
1551					|| (S_IWOTH & st.st_mode);
1552		BVolume volume(ref->device);
1553		editable = editable && !volume.IsReadOnly();
1554		_SetReadOnly(!editable);
1555	}
1556
1557	// update alignment
1558	switch (fTextView->Alignment()) {
1559		case B_ALIGN_LEFT:
1560		default:
1561			fAlignLeft->SetMarked(true);
1562			break;
1563		case B_ALIGN_CENTER:
1564			fAlignCenter->SetMarked(true);
1565			break;
1566		case B_ALIGN_RIGHT:
1567			fAlignRight->SetMarked(true);
1568			break;
1569	}
1570
1571	// update word wrapping
1572	fWrapItem->SetMarked(fTextView->DoesWordWrap());
1573	return B_OK;
1574}
1575
1576
1577#undef B_TRANSLATION_CONTEXT
1578#define B_TRANSLATION_CONTEXT "RevertToSavedAlert"
1579
1580
1581void
1582StyledEditWindow::_ReloadDocument(BMessage* message)
1583{
1584	entry_ref ref;
1585	const char* name;
1586
1587	if (fSaveMessage == NULL || message == NULL
1588		|| fSaveMessage->FindRef("directory", &ref) != B_OK
1589		|| fSaveMessage->FindString("name", &name) != B_OK)
1590		return;
1591
1592	BDirectory dir(&ref);
1593	status_t status = dir.InitCheck();
1594	BEntry entry;
1595	if (status == B_OK)
1596		status = entry.SetTo(&dir, name);
1597
1598	if (status == B_OK)
1599		status = entry.GetRef(&ref);
1600
1601	if (status != B_OK || !entry.Exists()) {
1602		BString alertText;
1603		bs_printf(&alertText,
1604			B_TRANSLATE("Cannot revert, file not found: \"%s\"."), name);
1605		_ShowAlert(alertText, B_TRANSLATE("OK"), "", "", B_STOP_ALERT);
1606		return;
1607	}
1608
1609	if (!fClean) {
1610		BString alertText;
1611		bs_printf(&alertText,
1612			B_TRANSLATE("\"%s\" has unsaved changes.\n"
1613				"Revert it to the last saved version? "), Title());
1614		if (_ShowAlert(alertText, B_TRANSLATE("Cancel"), B_TRANSLATE("Revert"),
1615			"", B_WARNING_ALERT) != 1)
1616			return;
1617	}
1618
1619	const BCharacterSet* charset
1620		= BCharacterSetRoster::GetCharacterSetByFontID(
1621			fTextView->GetEncoding());
1622	const char* forceEncoding = NULL;
1623	if (message->FindString("encoding", &forceEncoding) != B_OK) {
1624		if (charset != NULL)
1625			forceEncoding = charset->GetName();
1626	} else {
1627		if (charset != NULL) {
1628			// UTF8 id assumed equal to -1
1629			const uint32 idUTF8 = (uint32)-1;
1630			uint32 id = charset->GetConversionID();
1631			if (strcmp(forceEncoding, "next") == 0)
1632				id = id == B_MS_WINDOWS_1250_CONVERSION	? idUTF8 : id + 1;
1633			else if (strcmp(forceEncoding, "previous") == 0)
1634				id = id == idUTF8 ? B_MS_WINDOWS_1250_CONVERSION : id - 1;
1635			const BCharacterSet* newCharset
1636				= BCharacterSetRoster::GetCharacterSetByConversionID(id);
1637			if (newCharset != NULL)
1638				forceEncoding = newCharset->GetName();
1639		}
1640	}
1641
1642	BScrollBar* vertBar = fScrollView->ScrollBar(B_VERTICAL);
1643	float vertPos = vertBar != NULL ? vertBar->Value() : 0.f;
1644
1645	DisableUpdates();
1646
1647	fTextView->Reset();
1648
1649	status = _LoadFile(&ref, forceEncoding);
1650
1651	if (vertBar != NULL)
1652		vertBar->SetValue(vertPos);
1653
1654	EnableUpdates();
1655
1656	if (status != B_OK)
1657		return;
1658
1659#undef B_TRANSLATION_CONTEXT
1660#define B_TRANSLATION_CONTEXT "Menus"
1661
1662	// clear undo modes
1663	fUndoItem->SetLabel(B_TRANSLATE("Can't undo"));
1664	fUndoItem->SetEnabled(false);
1665	fUndoFlag = false;
1666	fCanUndo = false;
1667	fRedoFlag = false;
1668	fCanRedo = false;
1669
1670	// clear clean modes
1671	fSaveItem->SetEnabled(false);
1672
1673	fUndoCleans = false;
1674	fRedoCleans = false;
1675	fClean = true;
1676
1677	fNagOnNodeChange = true;
1678}
1679
1680
1681status_t
1682StyledEditWindow::_UnlockFile()
1683{
1684	_NodeMonitorSuspender nodeMonitorSuspender(this);
1685
1686	if (!fSaveMessage)
1687		return B_ERROR;
1688
1689	entry_ref dirRef;
1690	const char* name;
1691	if (fSaveMessage->FindRef("directory", &dirRef) != B_OK
1692		|| fSaveMessage->FindString("name", &name) != B_OK)
1693		return B_BAD_VALUE;
1694
1695	BDirectory dir(&dirRef);
1696	BEntry entry(&dir, name);
1697
1698	status_t status = dir.InitCheck();
1699	if (status != B_OK)
1700		return status;
1701
1702	status = entry.InitCheck();
1703	if (status != B_OK)
1704		return status;
1705
1706	struct stat st;
1707	BFile file(&entry, B_READ_WRITE);
1708	status = file.InitCheck();
1709	if (status != B_OK)
1710		return status;
1711
1712	status = file.GetStat(&st);
1713	if (status != B_OK)
1714		return status;
1715
1716	st.st_mode |= S_IWUSR;
1717	status = file.SetPermissions(st.st_mode);
1718	if (status == B_OK)
1719		_SetReadOnly(false);
1720
1721	return status;
1722}
1723
1724
1725bool
1726StyledEditWindow::_Search(BString string, bool caseSensitive, bool wrap,
1727	bool backSearch, bool scrollToOccurence)
1728{
1729	int32 start;
1730	int32 finish;
1731
1732	start = B_ERROR;
1733
1734	int32 length = string.Length();
1735	if (length == 0)
1736		return false;
1737
1738	BString viewText(fTextView->Text());
1739	int32 textStart, textFinish;
1740	fTextView->GetSelection(&textStart, &textFinish);
1741	if (backSearch) {
1742		if (caseSensitive)
1743			start = viewText.FindLast(string, textStart);
1744		else
1745			start = viewText.IFindLast(string, textStart);
1746	} else {
1747		if (caseSensitive)
1748			start = viewText.FindFirst(string, textFinish);
1749		else
1750			start = viewText.IFindFirst(string, textFinish);
1751	}
1752	if (start == B_ERROR && wrap) {
1753		if (backSearch) {
1754			if (caseSensitive)
1755				start = viewText.FindLast(string, viewText.Length());
1756			else
1757				start = viewText.IFindLast(string, viewText.Length());
1758		} else {
1759			if (caseSensitive)
1760				start = viewText.FindFirst(string, 0);
1761			else
1762				start = viewText.IFindFirst(string, 0);
1763		}
1764	}
1765
1766	if (start != B_ERROR) {
1767		finish = start + length;
1768		fTextView->Select(start, finish);
1769
1770		if (scrollToOccurence)
1771			fTextView->ScrollToSelection();
1772		return true;
1773	}
1774
1775	return false;
1776}
1777
1778
1779void
1780StyledEditWindow::_FindSelection()
1781{
1782	int32 selectionStart, selectionFinish;
1783	fTextView->GetSelection(&selectionStart, &selectionFinish);
1784
1785	int32 selectionLength = selectionFinish- selectionStart;
1786
1787	BString viewText = fTextView->Text();
1788	viewText.CopyInto(fStringToFind, selectionStart, selectionLength);
1789	fFindAgainItem->SetEnabled(true);
1790	_Search(fStringToFind, fCaseSensitive, fWrapAround, fBackSearch);
1791}
1792
1793
1794bool
1795StyledEditWindow::_Replace(BString findThis, BString replaceWith,
1796	bool caseSensitive, bool wrap, bool backSearch)
1797{
1798	if (_Search(findThis, caseSensitive, wrap, backSearch)) {
1799		int32 start;
1800		int32 finish;
1801		fTextView->GetSelection(&start, &finish);
1802
1803		_UpdateCleanUndoRedoSaveRevert();
1804		fTextView->SetSuppressChanges(true);
1805		fTextView->Delete(start, start + findThis.Length());
1806		fTextView->Insert(start, replaceWith.String(), replaceWith.Length());
1807		fTextView->SetSuppressChanges(false);
1808		fTextView->Select(start, start + replaceWith.Length());
1809		fTextView->ScrollToSelection();
1810		return true;
1811	}
1812
1813	return false;
1814}
1815
1816
1817void
1818StyledEditWindow::_ReplaceAll(BString findThis, BString replaceWith,
1819	bool caseSensitive)
1820{
1821	bool first = true;
1822	fTextView->SetSuppressChanges(true);
1823
1824	// start from the beginning of text
1825	fTextView->Select(0, 0);
1826
1827	// iterate occurences of findThis without wrapping around
1828	while (_Search(findThis, caseSensitive, false, false, false)) {
1829		if (first) {
1830			_UpdateCleanUndoRedoSaveRevert();
1831			first = false;
1832		}
1833		int32 start;
1834		int32 finish;
1835
1836		fTextView->GetSelection(&start, &finish);
1837		fTextView->Delete(start, start + findThis.Length());
1838		fTextView->Insert(start, replaceWith.String(), replaceWith.Length());
1839
1840		// advance the caret behind the inserted text
1841		start += replaceWith.Length();
1842		fTextView->Select(start, start);
1843	}
1844	fTextView->ScrollToSelection();
1845	fTextView->SetSuppressChanges(false);
1846}
1847
1848
1849void
1850StyledEditWindow::_SetFontSize(float fontSize)
1851{
1852	uint32 sameProperties;
1853	BFont font;
1854
1855	fTextView->GetFontAndColor(&font, &sameProperties);
1856	font.SetSize(fontSize);
1857	fTextView->SetFontAndColor(&font, B_FONT_SIZE);
1858
1859	_UpdateCleanUndoRedoSaveRevert();
1860}
1861
1862
1863void
1864StyledEditWindow::_SetFontColor(const rgb_color* color)
1865{
1866	uint32 sameProperties;
1867	BFont font;
1868
1869	fTextView->GetFontAndColor(&font, &sameProperties, NULL, NULL);
1870	fTextView->SetFontAndColor(&font, 0, color);
1871
1872	_UpdateCleanUndoRedoSaveRevert();
1873}
1874
1875
1876void
1877StyledEditWindow::_SetFontStyle(const char* fontFamily, const char* fontStyle)
1878{
1879	BFont font;
1880	uint32 sameProperties;
1881
1882	// find out what the old font was
1883	font_family oldFamily;
1884	font_style oldStyle;
1885	fTextView->GetFontAndColor(&font, &sameProperties);
1886	font.GetFamilyAndStyle(&oldFamily, &oldStyle);
1887
1888	// clear that family's bit on the menu, if necessary
1889	if (strcmp(oldFamily, fontFamily)) {
1890		BMenuItem* oldItem = fFontMenu->FindItem(oldFamily);
1891		if (oldItem != NULL) {
1892			oldItem->SetMarked(false);
1893			BMenu* menu = oldItem->Submenu();
1894			if (menu != NULL) {
1895				oldItem = menu->FindItem(oldStyle);
1896				if (oldItem != NULL)
1897					oldItem->SetMarked(false);
1898			}
1899		}
1900	}
1901
1902	font.SetFamilyAndStyle(fontFamily, fontStyle);
1903
1904	uint16 face = 0;
1905
1906	if (!(font.Face() & B_REGULAR_FACE))
1907		face = font.Face();
1908
1909	if (fBoldItem->IsMarked())
1910		face |= B_BOLD_FACE;
1911
1912	if (fItalicItem->IsMarked())
1913		face |= B_ITALIC_FACE;
1914
1915	if (fUnderlineItem->IsMarked())
1916		face |= B_UNDERSCORE_FACE;
1917
1918	font.SetFace(face);
1919
1920	fTextView->SetFontAndColor(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_FACE);
1921
1922	BMenuItem* superItem;
1923	superItem = fFontMenu->FindItem(fontFamily);
1924	if (superItem != NULL) {
1925		superItem->SetMarked(true);
1926		fCurrentFontItem = superItem;
1927	}
1928
1929	_UpdateCleanUndoRedoSaveRevert();
1930}
1931
1932
1933#undef B_TRANSLATION_CONTEXT
1934#define B_TRANSLATION_CONTEXT "Statistics"
1935
1936
1937int32
1938StyledEditWindow::_ShowStatistics()
1939{
1940	size_t words = 0;
1941	bool inWord = false;
1942	size_t length = fTextView->TextLength();
1943
1944	for (size_t i = 0; i < length; i++)	{
1945		if (BUnicodeChar::IsWhitespace(fTextView->Text()[i])) {
1946			inWord = false;
1947		} else if (!inWord)	{
1948			words++;
1949			inWord = true;
1950		}
1951	}
1952
1953	BString result;
1954	result << B_TRANSLATE("Document statistics") << '\n' << '\n'
1955		<< B_TRANSLATE("Lines:") << ' ' << fTextView->CountLines() << '\n'
1956		<< B_TRANSLATE("Characters:") << ' ' << length << '\n'
1957		<< B_TRANSLATE("Words:") << ' ' << words;
1958
1959	BAlert* alert = new BAlert("Statistics", result, B_TRANSLATE("OK"), NULL,
1960		NULL, B_WIDTH_AS_USUAL, B_EVEN_SPACING, B_INFO_ALERT);
1961	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1962
1963	return alert->Go();
1964}
1965
1966
1967void
1968StyledEditWindow::_SetReadOnly(bool readOnly)
1969{
1970	fReplaceItem->SetEnabled(!readOnly);
1971	fReplaceSameItem->SetEnabled(!readOnly);
1972	fFontMenu->SetEnabled(!readOnly);
1973	fAlignLeft->Menu()->SetEnabled(!readOnly);
1974	fWrapItem->SetEnabled(!readOnly);
1975	fTextView->MakeEditable(!readOnly);
1976}
1977
1978
1979#undef B_TRANSLATION_CONTEXT
1980#define B_TRANSLATION_CONTEXT "Menus"
1981
1982
1983void
1984StyledEditWindow::_UpdateCleanUndoRedoSaveRevert()
1985{
1986	fClean = false;
1987	fUndoCleans = false;
1988	fRedoCleans = false;
1989	fReloadItem->SetEnabled(fSaveMessage != NULL);
1990	fEncodingItem->SetEnabled(fSaveMessage != NULL);
1991	fSaveItem->SetEnabled(true);
1992	fUndoItem->SetLabel(B_TRANSLATE("Can't undo"));
1993	fUndoItem->SetEnabled(false);
1994	fCanUndo = false;
1995	fCanRedo = false;
1996}
1997
1998
1999int32
2000StyledEditWindow::_ShowAlert(const BString& text, const BString& label,
2001	const BString& label2, const BString& label3, alert_type type) const
2002{
2003	const char* button2 = NULL;
2004	if (label2.Length() > 0)
2005		button2 = label2.String();
2006
2007	const char* button3 = NULL;
2008	button_spacing spacing = B_EVEN_SPACING;
2009	if (label3.Length() > 0) {
2010		button3 = label3.String();
2011		spacing = B_OFFSET_SPACING;
2012	}
2013
2014	BAlert* alert = new BAlert("Alert", text.String(), label.String(), button2,
2015		button3, B_WIDTH_AS_USUAL, spacing, type);
2016	alert->SetShortcut(0, B_ESCAPE);
2017
2018	return alert->Go();
2019}
2020
2021
2022BMenu*
2023StyledEditWindow::_PopulateEncodingMenu(BMenu* menu, const char* currentEncoding)
2024{
2025	menu->SetRadioMode(true);
2026	BString encoding(currentEncoding);
2027	if (encoding.Length() == 0)
2028		encoding.SetTo("UTF-8");
2029
2030	BCharacterSetRoster roster;
2031	BCharacterSet charset;
2032	while (roster.GetNextCharacterSet(&charset) == B_OK) {
2033		const char* mime = charset.GetMIMEName();
2034		BString name(charset.GetPrintName());
2035
2036		if (mime)
2037			name << " (" << mime << ")";
2038
2039		BMessage *message = new BMessage(MENU_RELOAD);
2040		if (message != NULL) {
2041			message->AddString("encoding", charset.GetName());
2042			BMenuItem* item = new BMenuItem(name, message);
2043			if (encoding.Compare(charset.GetName()) == 0)
2044				item->SetMarked(true);
2045			menu->AddItem(item);
2046		}
2047	}
2048
2049	menu->AddSeparatorItem();
2050	BMessage *message = new BMessage(MENU_RELOAD);
2051	message->AddString("encoding", "auto");
2052	menu->AddItem(new BMenuItem(B_TRANSLATE("Autodetect"), message));
2053
2054	message = new BMessage(MENU_RELOAD);
2055	message->AddString("encoding", "next");
2056	AddShortcut(B_PAGE_DOWN, B_OPTION_KEY, message);
2057	message = new BMessage(MENU_RELOAD);
2058	message->AddString("encoding", "previous");
2059	AddShortcut(B_PAGE_UP, B_OPTION_KEY, message);
2060
2061	return menu;
2062}
2063
2064
2065#undef B_TRANSLATION_CONTEXT
2066#define B_TRANSLATION_CONTEXT "NodeMonitorAlerts"
2067
2068
2069void
2070StyledEditWindow::_ShowNodeChangeAlert(const char* name, bool removed)
2071{
2072	if (!fNagOnNodeChange)
2073		return;
2074
2075	BString alertText(removed ? B_TRANSLATE("File \"%file%\" was removed by "
2076		"another application, recover it?")
2077		: B_TRANSLATE("File \"%file%\" was modified by "
2078		"another application, reload it?"));
2079	alertText.ReplaceAll("%file%", name);
2080
2081	if (_ShowAlert(alertText, removed ? B_TRANSLATE("Recover")
2082			: B_TRANSLATE("Reload"), B_TRANSLATE("Ignore"), "",
2083			B_WARNING_ALERT) == 0)
2084	{
2085		if (!removed) {
2086			// supress the warning - user has already agreed
2087			fClean = true;
2088			BMessage msg(MENU_RELOAD);
2089			_ReloadDocument(&msg);
2090		} else
2091			Save();
2092	} else
2093		fNagOnNodeChange = false;
2094
2095	fSaveItem->SetEnabled(!fClean);
2096}
2097
2098
2099void
2100StyledEditWindow::_HandleNodeMonitorEvent(BMessage *message)
2101{
2102	int32 opcode = 0;
2103	if (message->FindInt32("opcode", &opcode) != B_OK)
2104		return;
2105
2106	if (opcode != B_ENTRY_CREATED
2107		&& message->FindInt64("node") != fNodeRef.node)
2108		// bypass foreign nodes' event
2109		return;
2110
2111	switch (opcode) {
2112		case B_STAT_CHANGED:
2113			{
2114				int32 fields = 0;
2115				if (message->FindInt32("fields", &fields) == B_OK
2116					&& (fields & (B_STAT_SIZE | B_STAT_MODIFICATION_TIME
2117							| B_STAT_MODE)) == 0)
2118					break;
2119
2120				const char* name = NULL;
2121				if (fSaveMessage->FindString("name", &name) != B_OK)
2122					break;
2123
2124				_ShowNodeChangeAlert(name, false);
2125			}
2126			break;
2127
2128		case B_ENTRY_MOVED:
2129			{
2130				int32 device = 0;
2131				int64 srcFolder = 0;
2132				int64 dstFolder = 0;
2133				const char* name = NULL;
2134				if (message->FindInt32("device", &device) != B_OK
2135					|| message->FindInt64("to directory", &dstFolder) != B_OK
2136					|| message->FindInt64("from directory", &srcFolder) != B_OK
2137					|| message->FindString("name", &name) != B_OK)
2138						break;
2139
2140				entry_ref newRef(device, dstFolder, name);
2141				BEntry entry(&newRef);
2142
2143				BEntry dirEntry;
2144				entry.GetParent(&dirEntry);
2145
2146				entry_ref ref;
2147				dirEntry.GetRef(&ref);
2148				fSaveMessage->ReplaceRef("directory", &ref);
2149				fSaveMessage->ReplaceString("name", name);
2150
2151				// store previous name - it may be useful in case
2152				// we have just moved to temporary copy of file (vim case)
2153				const char* sourceName = NULL;
2154				if (message->FindString("from name", &sourceName) == B_OK) {
2155					fSaveMessage->RemoveName("org.name");
2156					fSaveMessage->AddString("org.name", sourceName);
2157					fSaveMessage->RemoveName("move time");
2158					fSaveMessage->AddInt64("move time", system_time());
2159				}
2160
2161				SetTitle(name);
2162
2163				if (srcFolder != dstFolder) {
2164					_SwitchNodeMonitor(false);
2165					_SwitchNodeMonitor(true);
2166				}
2167				PostMessage(UPDATE_STATUS_REF);
2168			}
2169			break;
2170
2171		case B_ENTRY_REMOVED:
2172			{
2173				_SwitchNodeMonitor(false);
2174
2175				fClean = false;
2176
2177				// some editors like vim save files in following way:
2178				// 1) move t.txt -> t.txt~
2179				// 2) re-create t.txt and write data to it
2180				// 3) remove t.txt~
2181				// go to catch this case
2182				int32 device = 0;
2183				int64 directory = 0;
2184				BString orgName;
2185				if (fSaveMessage->FindString("org.name", &orgName) == B_OK
2186					&& message->FindInt32("device", &device) == B_OK
2187					&& message->FindInt64("directory", &directory) == B_OK)
2188				{
2189					// reuse the source name if it is not too old
2190					bigtime_t time = fSaveMessage->FindInt64("move time");
2191					if ((system_time() - time) < 1000000) {
2192						entry_ref ref(device, directory, orgName);
2193						BEntry entry(&ref);
2194						if (entry.InitCheck() == B_OK) {
2195							_SwitchNodeMonitor(true, &ref);
2196						}
2197
2198						fSaveMessage->ReplaceString("name", orgName);
2199						fSaveMessage->RemoveName("org.name");
2200						fSaveMessage->RemoveName("move time");
2201
2202						SetTitle(orgName);
2203						_ShowNodeChangeAlert(orgName, false);
2204						break;
2205					}
2206				}
2207
2208				const char* name = NULL;
2209				if (message->FindString("name", &name) != B_OK
2210					&& fSaveMessage->FindString("name", &name) != B_OK)
2211					name = "Unknown";
2212
2213				_ShowNodeChangeAlert(name, true);
2214				PostMessage(UPDATE_STATUS_REF);
2215			}
2216			break;
2217
2218		default:
2219			break;
2220	}
2221}
2222
2223
2224void
2225StyledEditWindow::_SwitchNodeMonitor(bool on, entry_ref* ref)
2226{
2227	if (!on) {
2228		watch_node(&fNodeRef, B_STOP_WATCHING, this);
2229		watch_node(&fFolderNodeRef, B_STOP_WATCHING, this);
2230		fNodeRef = node_ref();
2231		fFolderNodeRef = node_ref();
2232		return;
2233	}
2234
2235	BEntry entry, folderEntry;
2236
2237	if (ref != NULL) {
2238		entry.SetTo(ref, true);
2239		entry.GetParent(&folderEntry);
2240
2241	} else if (fSaveMessage != NULL) {
2242		entry_ref ref;
2243		const char* name = NULL;
2244		if (fSaveMessage->FindRef("directory", &ref) != B_OK
2245			|| fSaveMessage->FindString("name", &name) != B_OK)
2246			return;
2247
2248		BDirectory dir(&ref);
2249		entry.SetTo(&dir, name);
2250		folderEntry.SetTo(&ref);
2251
2252	} else
2253		return;
2254
2255	if (entry.InitCheck() != B_OK || folderEntry.InitCheck() != B_OK)
2256		return;
2257
2258	entry.GetNodeRef(&fNodeRef);
2259	folderEntry.GetNodeRef(&fFolderNodeRef);
2260
2261	watch_node(&fNodeRef, B_WATCH_STAT, this);
2262	watch_node(&fFolderNodeRef, B_WATCH_DIRECTORY, this);
2263}
2264