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