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