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