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