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