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