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