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