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