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