xref: /haiku/src/apps/stylededit/StyledEditWindow.cpp (revision adb0d19d561947362090081e81d90dde59142026)
1 /*
2  * Copyright 2002-2009, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Mattias Sundblad
7  *		Andrew Bachmann
8  */
9 
10 #include "Constants.h"
11 #include "ColorMenuItem.h"
12 #include "FindWindow.h"
13 #include "ReplaceWindow.h"
14 #include "StyledEditApp.h"
15 #include "StyledEditView.h"
16 #include "StyledEditWindow.h"
17 
18 #include <Alert.h>
19 #include <Autolock.h>
20 #include <CharacterSet.h>
21 #include <CharacterSetRoster.h>
22 #include <Clipboard.h>
23 #include <Debug.h>
24 #include <File.h>
25 #include <FilePanel.h>
26 #include <Menu.h>
27 #include <MenuBar.h>
28 #include <MenuItem.h>
29 #include <PrintJob.h>
30 #include <Rect.h>
31 #include <Roster.h>
32 #include <ScrollView.h>
33 #include <TextControl.h>
34 #include <TextView.h>
35 #include <TranslationUtils.h>
36 
37 using namespace BPrivate;
38 
39 
40 const float kLineViewWidth = 30.0;
41 
42 
43 StyledEditWindow::StyledEditWindow(BRect frame, int32 id, uint32 encoding)
44 	: BWindow(frame, "untitled", B_DOCUMENT_WINDOW, B_ASYNCHRONOUS_CONTROLS)
45 {
46 	InitWindow(encoding);
47 	BString unTitled("Untitled ");
48 	unTitled << id;
49 	SetTitle(unTitled.String());
50 	fSaveItem->SetEnabled(true);
51 		// allow saving empty files
52 	Show();
53 }
54 
55 
56 StyledEditWindow::StyledEditWindow(BRect frame, entry_ref *ref, uint32 encoding)
57 	: BWindow(frame, "untitled", B_DOCUMENT_WINDOW, B_ASYNCHRONOUS_CONTROLS)
58 {
59 	InitWindow(encoding);
60 	OpenFile(ref);
61 	Show();
62 }
63 
64 
65 StyledEditWindow::~StyledEditWindow()
66 {
67 	delete fSaveMessage;
68 	delete fPrintSettings;
69 	delete fSavePanel;
70 }
71 
72 
73 void
74 StyledEditWindow::InitWindow(uint32 encoding)
75 {
76 	fPrintSettings = NULL;
77 	fSaveMessage = NULL;
78 
79 	// undo modes
80 	fUndoFlag = false;
81 	fCanUndo = false;
82 	fRedoFlag = false;
83 	fCanRedo = false;
84 
85 	// clean modes
86 	fUndoCleans = false;
87 	fRedoCleans = false;
88 	fClean = true;
89 
90 	// search- state
91 	fReplaceString = "";
92 	fStringToFind = "";
93 	fCaseSens = false;
94 	fWrapAround = false;
95 	fBackSearch = false;
96 
97 	// add menubar
98 	fMenuBar = new BMenuBar(BRect(0, 0, 0, 0), "menubar");
99 	AddChild(fMenuBar);
100 
101 	// add textview and scrollview
102 
103 	BRect viewFrame = Bounds();
104 	viewFrame.top = fMenuBar->Bounds().Height() + 1;
105 	viewFrame.right -=  B_V_SCROLL_BAR_WIDTH;
106 	viewFrame.left = 0;
107 	viewFrame.bottom -= B_H_SCROLL_BAR_HEIGHT;
108 
109 	BRect textBounds = viewFrame;
110 	textBounds.OffsetTo(B_ORIGIN);
111 	textBounds.InsetBy(TEXT_INSET, TEXT_INSET);
112 
113 	fTextView= new StyledEditView(viewFrame, textBounds, this);
114 	fTextView->SetDoesUndo(true);
115 	fTextView->SetStylable(true);
116 	fTextView->SetEncoding(encoding);
117 
118 	fScrollView = new BScrollView("scrollview", fTextView, B_FOLLOW_ALL, 0,
119 		true, true, B_PLAIN_BORDER);
120 	AddChild(fScrollView);
121 	fTextView->MakeFocus(true);
122 
123 	// Add "File"-menu:
124 	BMenu* menu = new BMenu("File");
125 	fMenuBar->AddItem(menu);
126 
127 	BMenuItem* menuItem;
128 	menu->AddItem(menuItem = new BMenuItem("New", new BMessage(MENU_NEW), 'N'));
129 	menuItem->SetTarget(be_app);
130 
131 	menu->AddItem(menuItem = new BMenuItem(fRecentMenu = new BMenu("Open" B_UTF8_ELLIPSIS),
132 		new BMessage(MENU_OPEN)));
133 	menuItem->SetShortcut('O', 0);
134 	menuItem->SetTarget(be_app);
135 	menu->AddSeparatorItem();
136 
137 	menu->AddItem(fSaveItem = new BMenuItem("Save", new BMessage(MENU_SAVE), 'S'));
138 	fSaveItem->SetEnabled(false);
139 	menu->AddItem(menuItem = new BMenuItem("Save As" B_UTF8_ELLIPSIS, new BMessage(MENU_SAVEAS)));
140 	menuItem->SetShortcut('S',B_SHIFT_KEY);
141 	menuItem->SetEnabled(true);
142 
143 	menu->AddItem(fRevertItem = new BMenuItem("Revert to Saved" B_UTF8_ELLIPSIS,
144 		new BMessage(MENU_REVERT)));
145 	fRevertItem->SetEnabled(false);
146 	menu->AddItem(menuItem = new BMenuItem("Close", new BMessage(MENU_CLOSE), 'W'));
147 
148 	menu->AddSeparatorItem();
149 	menu->AddItem(menuItem = new BMenuItem("Page Setup" B_UTF8_ELLIPSIS, new BMessage(MENU_PAGESETUP)));
150 	menu->AddItem(menuItem = new BMenuItem("Print" B_UTF8_ELLIPSIS, new BMessage(MENU_PRINT), 'P'));
151 
152 	menu->AddSeparatorItem();
153 	menu->AddItem(menuItem = new BMenuItem("Quit", new BMessage(MENU_QUIT), 'Q'));
154 
155 	// Add the "Edit"-menu:
156 	menu = new BMenu("Edit");
157 	fMenuBar->AddItem(menu);
158 
159 	menu->AddItem(fUndoItem = new BMenuItem("Can't Undo", new BMessage(B_UNDO), 'Z'));
160 	fUndoItem->SetEnabled(false);
161 
162 	menu->AddSeparatorItem();
163 	menu->AddItem(fCutItem = new BMenuItem("Cut", new BMessage(B_CUT), 'X'));
164 	fCutItem->SetEnabled(false);
165 	fCutItem->SetTarget(fTextView);
166 
167 	menu->AddItem(fCopyItem = new BMenuItem("Copy", new BMessage(B_COPY), 'C'));
168 	fCopyItem->SetEnabled(false);
169 	fCopyItem->SetTarget(fTextView);
170 
171 	menu->AddItem(menuItem = new BMenuItem("Paste", new BMessage(B_PASTE), 'V'));
172 	menuItem->SetTarget(fTextView);
173 	menu->AddItem(fClearItem = new BMenuItem("Clear", new BMessage(MENU_CLEAR)));
174 	fClearItem->SetEnabled(false);
175 	fClearItem->SetTarget(fTextView);
176 
177 	menu->AddSeparatorItem();
178 	menu->AddItem(menuItem = new BMenuItem("Select All", new BMessage(B_SELECT_ALL), 'A'));
179 	menuItem->SetTarget(fTextView);
180 
181 	menu->AddSeparatorItem();
182 	menu->AddItem(menuItem = new BMenuItem("Find" B_UTF8_ELLIPSIS, new BMessage(MENU_FIND),'F'));
183 	menu->AddItem(fFindAgainItem= new BMenuItem("Find Again",new BMessage(MENU_FIND_AGAIN),'G'));
184 	fFindAgainItem->SetEnabled(false);
185 
186 	menu->AddItem(menuItem = new BMenuItem("Find Selection", new BMessage(MENU_FIND_SELECTION),'H'));
187 	menu->AddItem(menuItem = new BMenuItem("Replace" B_UTF8_ELLIPSIS, new BMessage(MENU_REPLACE),'R'));
188 	menu->AddItem(fReplaceSameItem = new BMenuItem("Replace Same", new BMessage(MENU_REPLACE_SAME),'T'));
189 	fReplaceSameItem->SetEnabled(false);
190 
191 	// Add the "Font"-menu:
192 	fFontMenu = new BMenu("Font");
193 	fMenuBar->AddItem(fFontMenu);
194 
195 	//"Size"-subMenu
196 	fFontSizeMenu = new BMenu("Size");
197 	fFontSizeMenu->SetRadioMode(true);
198 	fFontMenu->AddItem(fFontSizeMenu);
199 
200 	const int32 fontSizes[] = {9, 10, 11, 12, 14, 18, 24, 36, 48, 72};
201 	for (uint32 i = 0; i < sizeof(fontSizes) / sizeof(fontSizes[0]); i++) {
202 		BMessage* fontMessage = new BMessage(FONT_SIZE);
203 		fontMessage->AddFloat("size", fontSizes[i]);
204 
205 		char label[64];
206 		snprintf(label, sizeof(label), "%ld", fontSizes[i]);
207 		fFontSizeMenu->AddItem(menuItem = new BMenuItem(label, fontMessage));
208 
209 		if (fontSizes[i] == (int32)be_plain_font->Size())
210 			menuItem->SetMarked(true);
211 	}
212 
213 	// "Color"-subMenu
214 	fFontColorMenu = new BMenu("Color");
215 	fFontColorMenu->SetRadioMode(true);
216 	fFontMenu->AddItem(fFontColorMenu);
217 
218 	fFontColorMenu->AddItem(fBlackItem = new BMenuItem("Black", new BMessage(FONT_COLOR)));
219 	fBlackItem->SetMarked(true);
220 	fFontColorMenu->AddItem(fRedItem = new ColorMenuItem("Red", RED, new BMessage(FONT_COLOR)));
221 	fFontColorMenu->AddItem(fGreenItem = new ColorMenuItem("Green", GREEN, new BMessage(FONT_COLOR)));
222 	fFontColorMenu->AddItem(fBlueItem = new ColorMenuItem("Blue", BLUE, new BMessage(FONT_COLOR)));
223 	fFontColorMenu->AddItem(fCyanItem = new ColorMenuItem("Cyan", CYAN, new BMessage(FONT_COLOR)));
224 	fFontColorMenu->AddItem(fMagentaItem = new ColorMenuItem("Magenta", MAGENTA, new BMessage(FONT_COLOR)));
225 	fFontColorMenu->AddItem(fYellowItem = new ColorMenuItem("Yellow", YELLOW, new BMessage(FONT_COLOR)));
226 	fFontMenu->AddSeparatorItem();
227 
228 	// Available fonts, mark "be_plain_font" item
229 
230 	font_family plainFamily;
231 	font_style plainStyle;
232 	be_plain_font->GetFamilyAndStyle(&plainFamily, &plainStyle);
233 	fCurrentFontItem = 0;
234 
235 	BMenu* subMenu;
236 	int32 numFamilies = count_font_families();
237 	for (int32 i = 0; i < numFamilies; i++) {
238 		font_family family;
239 		if (get_font_family(i, &family) == B_OK) {
240 			subMenu = new BMenu(family);
241 			subMenu->SetRadioMode(true);
242 			fFontMenu->AddItem(menuItem = new BMenuItem(subMenu, new BMessage(FONT_FAMILY)));
243 
244 			if (!strcmp(plainFamily, family)) {
245 				menuItem->SetMarked(true);
246 				fCurrentFontItem = menuItem;
247 			}
248 
249 			int32 numStyles = count_font_styles(family);
250 			for (int32 j = 0; j < numStyles; j++) {
251 				font_style style;
252 				uint32 flags;
253 				if (get_font_style(family, j, &style, &flags) == B_OK) {
254 					subMenu->AddItem(menuItem = new BMenuItem(style,
255 						new BMessage(FONT_STYLE)));
256 
257 					if (!strcmp(plainStyle, style))
258 						menuItem->SetMarked(true);
259 				}
260 			}
261 		}
262 	}
263 
264 	// Add the "Document"-menu:
265 	menu = new BMenu("Document");
266 	fMenuBar->AddItem(menu);
267 
268 	// "Align"-subMenu:
269 	subMenu = new BMenu("Align");
270 	subMenu->SetRadioMode(true);
271 
272 	subMenu->AddItem(fAlignLeft = new BMenuItem("Left", new BMessage(ALIGN_LEFT)));
273 	menuItem->SetMarked(true);
274 
275 	subMenu->AddItem(fAlignCenter = new BMenuItem("Center", new BMessage(ALIGN_CENTER)));
276 	subMenu->AddItem(fAlignRight = new BMenuItem("Right", new BMessage(ALIGN_RIGHT)));
277 	menu->AddItem(subMenu);
278 	menu->AddItem(fWrapItem = new BMenuItem("Wrap Lines", new BMessage(WRAP_LINES)));
279 	fWrapItem->SetMarked(true);
280 
281 	fSavePanel = NULL;
282 	fSavePanelEncodingMenu = NULL;
283 		// build lazily
284 }
285 
286 
287 void
288 StyledEditWindow::MessageReceived(BMessage *message)
289 {
290 	if (message->WasDropped()) {
291 		entry_ref ref;
292 		if (message->FindRef("refs", 0, &ref)==B_OK) {
293 			message->what = B_REFS_RECEIVED;
294 			be_app->PostMessage(message);
295 		}
296 	}
297 
298 	switch (message->what) {
299 		// File menu
300 		case MENU_SAVE:
301 			if (!fSaveMessage)
302 				SaveAs();
303 			else
304 				Save(fSaveMessage);
305 			break;
306 
307 		case MENU_SAVEAS:
308 			SaveAs();
309 			break;
310 
311 		case B_SAVE_REQUESTED:
312 			Save(message);
313 			break;
314 
315 		case SAVE_THEN_QUIT:
316 			if (Save(message) == B_OK)
317 				Quit();
318 			break;
319 
320 		case MENU_REVERT:
321 			RevertToSaved();
322 			break;
323 
324 		case MENU_CLOSE:
325 			if (QuitRequested())
326 				Quit();
327 			break;
328 
329 		case MENU_PAGESETUP:
330 			PageSetup(fTextView->Window()->Title());
331 			break;
332 		case MENU_PRINT:
333 			Print(fTextView->Window()->Title());
334 			break;
335 		case MENU_QUIT:
336 			be_app->PostMessage(B_QUIT_REQUESTED);
337 			break;
338 
339 		// Edit menu
340 
341 		case B_UNDO:
342 			ASSERT(fCanUndo || fCanRedo);
343 			ASSERT(!(fCanUndo && fCanRedo));
344 			if (fCanUndo)
345 				fUndoFlag = true;
346 			if (fCanRedo)
347 				fRedoFlag = true;
348 
349 			fTextView->Undo(be_clipboard);
350 			break;
351 		case B_CUT:
352 			fTextView->Cut(be_clipboard);
353 			break;
354 		case B_COPY:
355 			fTextView->Copy(be_clipboard);
356 			break;
357 		case B_PASTE:
358 			fTextView->Paste(be_clipboard);
359 			break;
360 		case MENU_CLEAR:
361 			fTextView->Clear();
362 			break;
363 		case MENU_FIND:
364 		{
365 			BRect findWindowFrame(100, 100, 400, 235);
366 			BWindow* window = new FindWindow(findWindowFrame, this,
367 				&fStringToFind, fCaseSens, fWrapAround, fBackSearch);
368 			window->Show();
369 			break;
370 		}
371 		case MSG_SEARCH:
372 			message->FindString("findtext", &fStringToFind);
373 			fFindAgainItem->SetEnabled(true);
374 			message->FindBool("casesens", &fCaseSens);
375 			message->FindBool("wrap", &fWrapAround);
376 			message->FindBool("backsearch", &fBackSearch);
377 
378 			Search(fStringToFind, fCaseSens, fWrapAround, fBackSearch);
379 			break;
380 		case MENU_FIND_AGAIN:
381 			Search(fStringToFind, fCaseSens, fWrapAround, fBackSearch);
382 			break;
383 		case MENU_FIND_SELECTION:
384 			FindSelection();
385 			break;
386 		case MENU_REPLACE:
387 		{
388 			BRect replaceWindowFrame(100, 100, 400, 284);
389 			BWindow* window = new ReplaceWindow(replaceWindowFrame, this,
390 				&fStringToFind, &fReplaceString, fCaseSens, fWrapAround, fBackSearch);
391 			window->Show();
392 			break;
393 		}
394 		case MSG_REPLACE:
395 		{
396 			message->FindBool("casesens", &fCaseSens);
397 			message->FindBool("wrap", &fWrapAround);
398 			message->FindBool("backsearch", &fBackSearch);
399 
400 			message->FindString("FindText", &fStringToFind);
401 			message->FindString("ReplaceText", &fReplaceString);
402 
403 			fFindAgainItem->SetEnabled(true);
404 			fReplaceSameItem->SetEnabled(true);
405 
406 			Replace(fStringToFind, fReplaceString, fCaseSens, fWrapAround, fBackSearch);
407 			break;
408 		}
409 		case MENU_REPLACE_SAME:
410 			Replace(fStringToFind, fReplaceString, fCaseSens, fWrapAround, fBackSearch);
411 			break;
412 
413 		case MSG_REPLACE_ALL:
414 		{
415 			message->FindBool("casesens", &fCaseSens);
416 			message->FindString("FindText",&fStringToFind);
417 			message->FindString("ReplaceText",&fReplaceString);
418 
419 			bool allWindows;
420 			message->FindBool("allwindows", &allWindows);
421 
422 			fFindAgainItem->SetEnabled(true);
423 			fReplaceSameItem->SetEnabled(true);
424 
425 			if (allWindows)
426 				SearchAllWindows(fStringToFind, fReplaceString, fCaseSens);
427 			else
428 				ReplaceAll(fStringToFind, fReplaceString, fCaseSens);
429 			break;
430 		}
431 
432 		// Font menu
433 
434 		case FONT_SIZE:
435 		{
436 			float fontSize;
437 			if (message->FindFloat("size", &fontSize) == B_OK)
438 				SetFontSize(fontSize);
439 			break;
440 		}
441 		case FONT_FAMILY:
442 		{
443 			const char* fontFamily = NULL;
444 			const char* fontStyle = NULL;
445 			void* ptr;
446 			if (message->FindPointer("source", &ptr) == B_OK) {
447 				fCurrentFontItem = static_cast<BMenuItem*>(ptr);
448 				fontFamily = fCurrentFontItem->Label();
449 			}
450 			SetFontStyle(fontFamily, fontStyle);
451 			break;
452 		}
453 		case FONT_STYLE:
454 		{
455 			const char* fontFamily = NULL;
456 			const char* fontStyle = NULL;
457 			void* ptr;
458 			if (message->FindPointer("source", &ptr) == B_OK) {
459 				BMenuItem* item = static_cast<BMenuItem*>(ptr);
460 				fontStyle = item->Label();
461 				BMenu* menu = item->Menu();
462 				if (menu != NULL) {
463 					fCurrentFontItem = menu->Superitem();
464 					if (fCurrentFontItem != NULL)
465 						fontFamily = fCurrentFontItem->Label();
466 				}
467 			}
468 			SetFontStyle(fontFamily, fontStyle);
469 			break;
470 		}
471 		case FONT_COLOR:
472 		{
473 			void* ptr;
474 			if (message->FindPointer("source", &ptr) == B_OK) {
475 				if (ptr == fBlackItem)
476 					SetFontColor(&BLACK);
477 				else if (ptr == fRedItem)
478 					SetFontColor(&RED);
479 				else if (ptr == fGreenItem)
480 					SetFontColor(&GREEN);
481 				else if (ptr == fBlueItem)
482 					SetFontColor(&BLUE);
483 				else if (ptr == fCyanItem)
484 					SetFontColor(&CYAN);
485 				else if (ptr == fMagentaItem)
486 					SetFontColor(&MAGENTA);
487 				else if (ptr == fYellowItem)
488 					SetFontColor(&YELLOW);
489 			}
490 			break;
491 		}
492 
493 		// Document menu
494 
495 		case ALIGN_LEFT:
496 			fTextView->SetAlignment(B_ALIGN_LEFT);
497 			_UpdateCleanUndoRedoSaveRevert();
498 			break;
499 		case ALIGN_CENTER:
500 			fTextView->SetAlignment(B_ALIGN_CENTER);
501 			_UpdateCleanUndoRedoSaveRevert();
502 			break;
503 		case ALIGN_RIGHT:
504 			fTextView->SetAlignment(B_ALIGN_RIGHT);
505 			_UpdateCleanUndoRedoSaveRevert();
506 			break;
507 		case WRAP_LINES:
508 		{
509 			BRect textRect(fTextView->Bounds());
510 			textRect.OffsetTo(B_ORIGIN);
511 			textRect.InsetBy(TEXT_INSET,TEXT_INSET);
512 			if (fTextView->DoesWordWrap()) {
513 				fTextView->SetWordWrap(false);
514 				fWrapItem->SetMarked(false);
515 				// the width comes from stylededit R5. TODO: find a better way
516 				textRect.SetRightBottom(BPoint(1500.0, textRect.RightBottom().y));
517 			} else {
518 				fTextView->SetWordWrap(true);
519 				fWrapItem->SetMarked(true);
520 			}
521 			fTextView->SetTextRect(textRect);
522 
523 			_UpdateCleanUndoRedoSaveRevert();
524 			break;
525 		}
526 		case ENABLE_ITEMS:
527 			fCutItem->SetEnabled(true);
528 			fCopyItem->SetEnabled(true);
529 			fClearItem->SetEnabled(true);
530 			break;
531 		case DISABLE_ITEMS:
532 			fCutItem->SetEnabled(false);
533 			fCopyItem->SetEnabled(false);
534 			fClearItem->SetEnabled(false);
535 			break;
536 		case TEXT_CHANGED:
537 			if (fUndoFlag) {
538 				if (fUndoCleans) {
539 					// we cleaned!
540 					fClean = true;
541 					fUndoCleans = false;
542 				} else if (fClean) {
543 				   // if we were clean
544 				   // then a redo will make us clean again
545 				   fRedoCleans = true;
546 				   fClean = false;
547 				}
548 				// set mode
549 				fCanUndo = false;
550 				fCanRedo = true;
551 				fUndoItem->SetLabel("Redo Typing");
552 				fUndoItem->SetEnabled(true);
553 				fUndoFlag = false;
554 			} else {
555 				if (fRedoFlag && fRedoCleans) {
556 					// we cleaned!
557 					fClean = true;
558 					fRedoCleans = false;
559 				} else if (fClean) {
560 					// if we were clean
561 					// then an undo will make us clean again
562 					fUndoCleans = true;
563 					fClean = false;
564 				} else {
565 					// no more cleaning from undo now...
566 					fUndoCleans = false;
567 				}
568 				// set mode
569 				fCanUndo = true;
570 				fCanRedo = false;
571 				fUndoItem->SetLabel("Undo Typing");
572 				fUndoItem->SetEnabled(true);
573 				fRedoFlag = false;
574 			}
575 			if (fClean) {
576 				fRevertItem->SetEnabled(false);
577 				fSaveItem->SetEnabled(fSaveMessage == NULL);
578 			} else {
579 				fRevertItem->SetEnabled(fSaveMessage != NULL);
580 				fSaveItem->SetEnabled(true);
581 			}
582 			break;
583 
584 		case SAVE_AS_ENCODING:
585 			void* ptr;
586 			if (message->FindPointer("source", &ptr) == B_OK
587 				&& fSavePanelEncodingMenu != NULL) {
588 				fTextView->SetEncoding((uint32)fSavePanelEncodingMenu->IndexOf((BMenuItem*)ptr));
589 			}
590 			break;
591 
592 		default:
593 			BWindow::MessageReceived(message);
594 			break;
595 	}
596 }
597 
598 
599 void
600 StyledEditWindow::MenusBeginning()
601 {
602 	// set up the recent documents menu
603 	BMessage documents;
604 	be_roster->GetRecentDocuments(&documents, 9, NULL, APP_SIGNATURE);
605 
606 	// delete old items..
607 	//    shatty: it would be preferable to keep the old
608 	//            menu around instead of continuously thrashing
609 	//            the menu, but unfortunately there does not
610 	//            seem to be a straightforward way to update it
611 	// going backwards may simplify memory management
612 	for (int i = fRecentMenu->CountItems(); i-- > 0;) {
613 		delete fRecentMenu->RemoveItem(i);
614 	}
615 
616 	// add new items
617 	int count = 0;
618 	entry_ref ref;
619 	while (documents.FindRef("refs", count++, &ref) == B_OK) {
620 		if (ref.device != -1 && ref.directory != -1) {
621 			// sanity check passed
622 			BMessage* openRecent = new BMessage(B_REFS_RECEIVED);
623 			openRecent->AddRef("refs", &ref);
624 			BMenuItem* item = new BMenuItem(ref.name, openRecent);
625 			item->SetTarget(be_app);
626 			fRecentMenu->AddItem(item);
627 		}
628 	}
629 
630 	// update the font menu
631 	// unselect the old values
632 	if (fCurrentFontItem != NULL)
633 		fCurrentFontItem->SetMarked(false);
634 
635 	BMenuItem* oldColorItem = fFontColorMenu->FindMarked();
636 	if (oldColorItem != NULL)
637 		oldColorItem->SetMarked(false);
638 
639 	BMenuItem* oldSizeItem = fFontSizeMenu->FindMarked();
640 	if (oldSizeItem != NULL)
641 		oldSizeItem->SetMarked(false);
642 
643 	// find the current font, color, size
644 	BFont font;
645 	uint32 sameProperties;
646 	rgb_color color = BLACK;
647 	bool sameColor;
648 	fTextView->GetFontAndColor(&font, &sameProperties, &color, &sameColor);
649 
650 	if (sameColor && color.alpha == 255) {
651 		// select the current color
652 		if (color.red == 0) {
653 			if (color.green == 0) {
654 				if (color.blue == 0) {
655 					fBlackItem->SetMarked(true);
656 				} else if (color.blue == 255) {
657 					fBlueItem->SetMarked(true);
658 				}
659 			} else if (color.green == 255) {
660 				if (color.blue == 0) {
661 					fGreenItem->SetMarked(true);
662 				} else if (color.blue == 255) {
663 					fCyanItem->SetMarked(true);
664 				}
665 			}
666 		} else if (color.red == 255) {
667 			if (color.green == 0) {
668 				if (color.blue == 0) {
669 					fRedItem->SetMarked(true);
670 				} else if (color.blue == 255) {
671 					fMagentaItem->SetMarked(true);
672 				}
673 			} else if (color.green == 255) {
674 				if (color.blue == 0) {
675 					fYellowItem->SetMarked(true);
676 				}
677 			}
678 		}
679 	}
680 
681 	if (sameProperties & B_FONT_SIZE) {
682 		if ((int)font.Size() == font.Size()) {
683 			// select the current font size
684 			char fontSizeStr[16];
685 			snprintf(fontSizeStr, 15, "%i", (int)font.Size());
686 			BMenuItem* item = fFontSizeMenu->FindItem(fontSizeStr);
687 			if (item != NULL)
688 				item->SetMarked(true);
689 		}
690 	}
691 
692 	if (sameProperties & B_FONT_FAMILY_AND_STYLE) {
693 		font_family family;
694 		font_style style;
695 		font.GetFamilyAndStyle(&family, &style);
696 		fCurrentFontItem = fFontMenu->FindItem(family);
697 		if (fCurrentFontItem != NULL) {
698 			fCurrentFontItem->SetMarked(true);
699 			BMenu* menu = fCurrentFontItem->Submenu();
700 			if (menu != NULL) {
701 				BMenuItem* item = menu->FindItem(style);
702 				if (item != NULL)
703 					item->SetMarked(true);
704 			}
705 		}
706 	}
707 
708 	switch (fTextView->Alignment()) {
709 		case B_ALIGN_LEFT:
710 		default:
711 			fAlignLeft->SetMarked(true);
712 			break;
713 		case B_ALIGN_CENTER:
714 			fAlignCenter->SetMarked(true);
715 			break;
716 		case B_ALIGN_RIGHT:
717 			fAlignRight->SetMarked(true);
718 			break;
719 	}
720 }
721 
722 
723 void
724 StyledEditWindow::Quit()
725 {
726 	styled_edit_app->CloseDocument();
727 	BWindow::Quit();
728 }
729 
730 
731 bool
732 StyledEditWindow::QuitRequested()
733 {
734 	if (fClean)
735 		return true;
736 
737 	BString alertText("Save changes to the document \"");
738 	alertText<< Title() <<"\"? ";
739 	int32 index = _ShowAlert(alertText, "Cancel", "Don't Save", "Save",
740 		B_WARNING_ALERT);
741 
742 	if (index == 0)
743 		return false;	// "cancel": dont save, dont close the window
744 
745 	if (index == 1)
746 		return true;	// "don't save": just close the window
747 
748 	if (!fSaveMessage) {
749 		SaveAs(new BMessage(SAVE_THEN_QUIT));
750 		return false;
751 	}
752 
753 	return Save() == B_OK;
754 }
755 
756 
757 status_t
758 StyledEditWindow::Save(BMessage *message)
759 {
760 	if (!message)
761 		message = fSaveMessage;
762 
763 	if (!message)
764 		return B_ERROR;
765 
766 	entry_ref dirRef;
767 	const char* name;
768 	if (message->FindRef("directory", &dirRef) != B_OK
769 		|| message->FindString("name", &name) != B_OK)
770 		return B_BAD_VALUE;
771 
772 	BDirectory dir(&dirRef);
773 	BEntry entry(&dir, name);
774 
775 	status_t status = B_ERROR;
776 	if (dir.InitCheck() == B_OK && entry.InitCheck() == B_OK) {
777 		BFile file(&entry, B_READ_WRITE | B_CREATE_FILE);
778 		if (file.InitCheck() == B_OK)
779 			status = fTextView->WriteStyledEditFile(&file);
780 	}
781 
782 	if (status != B_OK) {
783 		BString alertText("Error saving \"");
784 		alertText << name << "\":\n" << strerror(status);
785 
786 		_ShowAlert(alertText, "OK", "", "", B_STOP_ALERT);
787 		return status;
788 	}
789 
790 	SetTitle(name);
791 
792 	if (fSaveMessage != message) {
793 		delete fSaveMessage;
794 		fSaveMessage = new BMessage(*message);
795 	}
796 
797 	entry_ref ref;
798 	if (entry.GetRef(&ref) == B_OK)
799 		be_roster->AddToRecentDocuments(&ref, APP_SIGNATURE);
800 
801 	// clear clean modes
802 	fSaveItem->SetEnabled(false);
803 	fRevertItem->SetEnabled(false);
804 	fUndoCleans = false;
805 	fRedoCleans = false;
806 	fClean = true;
807 	return status;
808 }
809 
810 
811 status_t
812 StyledEditWindow::SaveAs(BMessage *message)
813 {
814 	if (fSavePanel == NULL) {
815 		entry_ref* directory = NULL;
816 		if (fSaveMessage != NULL) {
817 			entry_ref dirRef;
818 			if (fSaveMessage->FindRef("directory", &dirRef) == B_OK)
819 				directory = &dirRef;
820 		}
821 
822 		BMessenger target(this);
823 		fSavePanel = new BFilePanel(B_SAVE_PANEL, &target,
824 			directory, B_FILE_NODE, false);
825 
826 		BMenuBar* menuBar = dynamic_cast<BMenuBar*>(
827 			fSavePanel->Window()->FindView("MenuBar"));
828 
829 		fSavePanelEncodingMenu= new BMenu("Encoding");
830 		menuBar->AddItem(fSavePanelEncodingMenu);
831 		fSavePanelEncodingMenu->SetRadioMode(true);
832 
833 		BCharacterSetRoster roster;
834 		BCharacterSet charset;
835 		while (roster.GetNextCharacterSet(&charset) == B_NO_ERROR) {
836 			BString name(charset.GetPrintName());
837 			const char* mime = charset.GetMIMEName();
838 			if (mime) {
839 				name.Append(" (");
840 				name.Append(mime);
841 				name.Append(")");
842 			}
843 			BMenuItem * item = new BMenuItem(name.String(), new BMessage(SAVE_AS_ENCODING));
844 			item->SetTarget(this);
845 			fSavePanelEncodingMenu->AddItem(item);
846 			if (charset.GetFontID() == fTextView->GetEncoding())
847 				item->SetMarked(true);
848 		}
849 	}
850 
851 	fSavePanel->SetSaveText(Title());
852 	if (message != NULL)
853 		fSavePanel->SetMessage(message);
854 
855 	fSavePanel->Show();
856 	return B_OK;
857 }
858 
859 
860 status_t
861 StyledEditWindow::_LoadFile(entry_ref* ref)
862 {
863 	BEntry entry(ref, true);
864 		// traverse an eventual link
865 
866 	status_t status = entry.InitCheck();
867 	if (status == B_OK && entry.IsDirectory())
868 		status = B_IS_A_DIRECTORY;
869 
870 	BFile file;
871 	if (status == B_OK)
872 		status = file.SetTo(&entry, B_READ_ONLY);
873 	if (status == B_OK)
874 		status = fTextView->GetStyledText(&file);
875 
876 	if (status == B_ENTRY_NOT_FOUND) {
877 		// Treat non-existing files consideratley; we just want to get an
878 		// empty window for them - to create this new document
879 		status = B_OK;
880 	}
881 
882 	if (status != B_OK) {
883 		// If an error occured, bail out and tell the user what happened
884 		BEntry entry(ref, true);
885 		char name[B_FILE_NAME_LENGTH];
886 		if (entry.GetName(name) != B_OK)
887 			strcpy(name, "???");
888 
889 		BString text("Error loading \"");
890 		text << name << "\":\n\t";
891 		if (status == B_BAD_TYPE)
892 			text << "Unsupported format";
893 		else
894 			text << strerror(status);
895 
896 		_ShowAlert(text, "OK", "", "", B_STOP_ALERT);
897 		return status;
898 	}
899 
900 	// update alignment
901 	switch (fTextView->Alignment()) {
902 		case B_ALIGN_LEFT:
903 		default:
904 			fAlignLeft->SetMarked(true);
905 			break;
906 		case B_ALIGN_CENTER:
907 			fAlignCenter->SetMarked(true);
908 			break;
909 		case B_ALIGN_RIGHT:
910 			fAlignRight->SetMarked(true);
911 			break;
912 	}
913 
914 	// update word wrapping
915 	fWrapItem->SetMarked(fTextView->DoesWordWrap());
916 	return B_OK;
917 }
918 
919 
920 void
921 StyledEditWindow::OpenFile(entry_ref* ref)
922 {
923 	if (_LoadFile(ref) != B_OK) {
924 		fSaveItem->SetEnabled(true);
925 			// allow saving new files
926 		return;
927 	}
928 
929 	be_roster->AddToRecentDocuments(ref, APP_SIGNATURE);
930 	fSaveMessage = new BMessage(B_SAVE_REQUESTED);
931 	if (fSaveMessage) {
932 		BEntry entry(ref, true);
933 		BEntry parent;
934 		entry_ref parentRef;
935 		char name[B_FILE_NAME_LENGTH];
936 
937 		entry.GetParent(&parent);
938 		entry.GetName(name);
939 		parent.GetRef(&parentRef);
940 		fSaveMessage->AddRef("directory", &parentRef);
941 		fSaveMessage->AddString("name", name);
942 		SetTitle(name);
943 	}
944 	fTextView->Select(0, 0);
945 }
946 
947 
948 void
949 StyledEditWindow::RevertToSaved()
950 {
951 	entry_ref ref;
952 	const char *name;
953 
954 	fSaveMessage->FindRef("directory", &ref);
955 	fSaveMessage->FindString("name", &name);
956 
957 	BDirectory dir(&ref);
958 	status_t status = dir.InitCheck();
959 	BEntry entry;
960 	if (status == B_OK)
961 		status = entry.SetTo(&dir, name);
962 
963 	if (status == B_OK)
964 		status = entry.GetRef(&ref);
965 
966 	if (status != B_OK || !entry.Exists()) {
967 		BString alertText("Cannot revert, file not found: \"");
968 		alertText << name << "\".";
969 		_ShowAlert(alertText, "OK", "", "", B_STOP_ALERT);
970 		return;
971 	}
972 
973 	BString alertText("Revert to the last version of \"");
974 	alertText << Title() << "\"? ";
975 	if (_ShowAlert(alertText, "Cancel", "OK", "", B_WARNING_ALERT) != 1)
976 		return;
977 
978 	fTextView->Reset();
979 	if (_LoadFile(&ref) != B_OK)
980 		return;
981 
982 	// clear undo modes
983 	fUndoItem->SetLabel("Can't Undo");
984 	fUndoItem->SetEnabled(false);
985 	fUndoFlag = false;
986 	fCanUndo = false;
987 	fRedoFlag = false;
988 	fCanRedo = false;
989 
990 	// clear clean modes
991 	fSaveItem->SetEnabled(false);
992 	fRevertItem->SetEnabled(false);
993 	fUndoCleans = false;
994 	fRedoCleans = false;
995 	fClean = true;
996 }
997 
998 
999 status_t
1000 StyledEditWindow::PageSetup(const char* documentName)
1001 {
1002 	BPrintJob printJob(documentName);
1003 
1004 	if (fPrintSettings != NULL)
1005 		printJob.SetSettings(new BMessage(*fPrintSettings));
1006 
1007 	status_t result = printJob.ConfigPage();
1008 	if (result == B_OK) {
1009 		delete fPrintSettings;
1010 		fPrintSettings = printJob.Settings();
1011 	}
1012 
1013 	return result;
1014 }
1015 
1016 
1017 void
1018 StyledEditWindow::Print(const char* documentName)
1019 {
1020 	BPrintJob printJob(documentName);
1021 	if (fPrintSettings)
1022 		printJob.SetSettings(new BMessage(*fPrintSettings));
1023 
1024 	if (printJob.ConfigJob() != B_OK)
1025  		return;
1026 
1027 	delete fPrintSettings;
1028 	fPrintSettings = printJob.Settings();
1029 
1030 	// information from printJob
1031 	BRect printableRect = printJob.PrintableRect();
1032 	int32 firstPage = printJob.FirstPage();
1033 	int32 lastPage = printJob.LastPage();
1034 
1035 	// lines eventually to be used to compute pages to print
1036 	int32 firstLine = 0;
1037 	int32 lastLine = fTextView->CountLines();
1038 
1039 	// values to be computed
1040 	int32 pagesInDocument = 1;
1041 	int32 linesInDocument = fTextView->CountLines();
1042 
1043 	int32 currentLine = 0;
1044 	while (currentLine < linesInDocument) {
1045 		float currentHeight = 0;
1046 		while (currentHeight < printableRect.Height() && currentLine < linesInDocument) {
1047 			currentHeight += fTextView->LineHeight(currentLine);
1048 			if (currentHeight < printableRect.Height())
1049 				currentLine++;
1050 		}
1051 		if (pagesInDocument == lastPage)
1052 			lastLine = currentLine - 1;
1053 
1054 		if (currentHeight >= printableRect.Height()) {
1055 			pagesInDocument++;
1056 			if (pagesInDocument == firstPage)
1057 				firstLine = currentLine;
1058 		}
1059 	}
1060 
1061 	if (lastPage > pagesInDocument - 1) {
1062 		lastPage = pagesInDocument - 1;
1063 		lastLine = currentLine - 1;
1064 	}
1065 
1066 
1067 	printJob.BeginJob();
1068 	if (fTextView->CountLines() > 0 && fTextView->TextLength() > 0) {
1069 		int32 printLine = firstLine;
1070 		while (printLine <= lastLine) {
1071 			float currentHeight = 0;
1072 			int32 firstLineOnPage = printLine;
1073 			while (currentHeight < printableRect.Height() && printLine <= lastLine) {
1074 				currentHeight += fTextView->LineHeight(printLine);
1075 				if (currentHeight < printableRect.Height())
1076 					printLine++;
1077 			}
1078 
1079 			float top = 0;
1080 			if (firstLineOnPage != 0)
1081 				top = fTextView->TextHeight(0, firstLineOnPage - 1);
1082 
1083 			float bottom = fTextView->TextHeight(0, printLine - 1);
1084 			BRect textRect(0.0, top + TEXT_INSET, printableRect.Width(), bottom + TEXT_INSET);
1085 			printJob.DrawView(fTextView, textRect, B_ORIGIN);
1086 			printJob.SpoolPage();
1087 		}
1088 	}
1089 
1090 
1091 	printJob.CommitJob();
1092 }
1093 
1094 
1095 bool
1096 StyledEditWindow::Search(BString string, bool caseSensitive, bool wrap, bool backsearch)
1097 {
1098 	int32 start;
1099 	int32 finish;
1100 
1101 	start = B_ERROR;
1102 
1103 	int32 length = string.Length();
1104 	if (length == 0)
1105 		return false;
1106 
1107 	BString viewText(fTextView->Text());
1108 	int32 textStart, textFinish;
1109 	fTextView->GetSelection(&textStart, &textFinish);
1110 	if (backsearch) {
1111 		if (caseSensitive) {
1112 			start = viewText.FindLast(string, textStart);
1113 		} else {
1114 			start = viewText.IFindLast(string, textStart);
1115 		}
1116 	} else {
1117 		if (caseSensitive == true) {
1118 			start = viewText.FindFirst(string, textFinish);
1119 		} else {
1120 			start = viewText.IFindFirst(string, textFinish);
1121 		}
1122 	}
1123 	if (start == B_ERROR && wrap) {
1124 		if (backsearch) {
1125 			if (caseSensitive) {
1126 				start = viewText.FindLast(string, viewText.Length());
1127 			} else {
1128 				start = viewText.IFindLast(string, viewText.Length());
1129 			}
1130 		} else {
1131 			if (caseSensitive) {
1132 				start = viewText.FindFirst(string, 0);
1133 			} else {
1134 				start = viewText.IFindFirst(string, 0);
1135 			}
1136 		}
1137 	}
1138 
1139 	if (start != B_ERROR) {
1140 		finish = start + length;
1141 		fTextView->Select(start, finish);
1142 		fTextView->ScrollToSelection();
1143 		return true;
1144 	}
1145 
1146 	return false;
1147 }
1148 
1149 
1150 void
1151 StyledEditWindow::FindSelection()
1152 {
1153 	int32 selectionStart, selectionFinish;
1154 	fTextView->GetSelection(&selectionStart, &selectionFinish);
1155 
1156 	int32 selectionLength = selectionFinish- selectionStart;
1157 
1158 	BString viewText = fTextView->Text();
1159 	viewText.CopyInto(fStringToFind, selectionStart, selectionLength);
1160 	fFindAgainItem->SetEnabled(true);
1161 	Search(fStringToFind, fCaseSens, fWrapAround, fBackSearch);
1162 }
1163 
1164 
1165 bool
1166 StyledEditWindow::Replace(BString findthis, BString replaceWith, bool caseSensitive,
1167 	bool wrap, bool backsearch)
1168 {
1169 	if (Search(findthis, caseSensitive, wrap, backsearch)) {
1170 		int32 start, finish;
1171 		fTextView->GetSelection(&start, &finish);
1172 
1173 		fTextView->Delete(start, start + findthis.Length());
1174 		fTextView->Insert(start, replaceWith.String(), replaceWith.Length());
1175 		fTextView->Select(start, start + replaceWith.Length());
1176 		fTextView->ScrollToSelection();
1177 		return true;
1178 	}
1179 
1180 	return false;
1181 }
1182 
1183 
1184 void
1185 StyledEditWindow::ReplaceAll(BString findIt, BString replaceWith, bool caseSensitive)
1186 {
1187 	BString viewText(fTextView->Text());
1188 	if (caseSensitive)
1189 		viewText.ReplaceAll(findIt.String(), replaceWith.String());
1190 	else
1191 		viewText.IReplaceAll(findIt.String(), replaceWith.String());
1192 
1193 	if (viewText.Compare(fTextView->Text()) == 0) {
1194 		// they are the same
1195 		return;
1196 	}
1197 
1198 	int32 textStart, textFinish;
1199 	fTextView->GetSelection(&textStart, &textFinish);
1200 
1201 	fTextView->SetText(viewText.String());
1202 
1203 	if (viewText.Length() < textStart)
1204 		textStart = viewText.Length();
1205 	if (viewText.Length() < textFinish)
1206 		textFinish = viewText.Length();
1207 
1208 	fTextView->Select(textStart,textFinish);
1209 	fTextView->ScrollToSelection();
1210 
1211 	_UpdateCleanUndoRedoSaveRevert();
1212 }
1213 
1214 
1215 void
1216 StyledEditWindow::SearchAllWindows(BString find, BString replace, bool caseSensitive)
1217 {
1218 	int32 numWindows;
1219 	numWindows = be_app->CountWindows();
1220 
1221 	BMessage *message;
1222 	message= new BMessage(MSG_REPLACE_ALL);
1223 	message->AddString("FindText", find);
1224 	message->AddString("ReplaceText", replace);
1225 	message->AddBool("casesens", caseSensitive);
1226 
1227 	while (numWindows >= 0) {
1228 		StyledEditWindow *window = dynamic_cast<StyledEditWindow *>(
1229 			be_app->WindowAt(numWindows));
1230 
1231 		BMessenger messenger(window);
1232 		messenger.SendMessage(message);
1233 
1234 		numWindows--;
1235 	}
1236 }
1237 
1238 
1239 void
1240 StyledEditWindow::SetFontSize(float fontSize)
1241 {
1242 	uint32 sameProperties;
1243 	BFont font;
1244 
1245 	fTextView->GetFontAndColor(&font, &sameProperties);
1246 	font.SetSize(fontSize);
1247 	fTextView->SetFontAndColor(&font, B_FONT_SIZE);
1248 
1249 	_UpdateCleanUndoRedoSaveRevert();
1250 }
1251 
1252 
1253 void
1254 StyledEditWindow::SetFontColor(const rgb_color *color)
1255 {
1256 	uint32 sameProperties;
1257 	BFont font;
1258 
1259 	fTextView->GetFontAndColor(&font, &sameProperties, NULL, NULL);
1260 	fTextView->SetFontAndColor(&font, 0, color);
1261 
1262 	_UpdateCleanUndoRedoSaveRevert();
1263 }
1264 
1265 
1266 void
1267 StyledEditWindow::SetFontStyle(const char *fontFamily, const char *fontStyle)
1268 {
1269 	BFont font;
1270 	uint32 sameProperties;
1271 
1272 	// find out what the old font was
1273 	font_family oldFamily;
1274 	font_style oldStyle;
1275 	fTextView->GetFontAndColor(&font, &sameProperties);
1276 	font.GetFamilyAndStyle(&oldFamily, &oldStyle);
1277 
1278 	// clear that family's bit on the menu, if necessary
1279 	if (strcmp(oldFamily, fontFamily)) {
1280 		BMenuItem* oldItem = fFontMenu->FindItem(oldFamily);
1281 		if (oldItem != NULL)
1282 			oldItem->SetMarked(false);
1283 	}
1284 
1285 	font.SetFamilyAndStyle(fontFamily, fontStyle);
1286 	fTextView->SetFontAndColor(&font);
1287 
1288 	BMenuItem* superItem;
1289 	superItem = fFontMenu->FindItem(fontFamily);
1290 	if (superItem != NULL)
1291 		superItem->SetMarked(true);
1292 
1293 	_UpdateCleanUndoRedoSaveRevert();
1294 }
1295 
1296 
1297 void
1298 StyledEditWindow::_UpdateCleanUndoRedoSaveRevert()
1299 {
1300 	fClean = false;
1301 	fUndoCleans = false;
1302 	fRedoCleans = false;
1303 	fRevertItem->SetEnabled(fSaveMessage != NULL);
1304 	fSaveItem->SetEnabled(true);
1305 	fUndoItem->SetLabel("Can't Undo");
1306 	fUndoItem->SetEnabled(false);
1307 	fCanUndo = false;
1308 	fCanRedo = false;
1309 }
1310 
1311 
1312 int32
1313 StyledEditWindow::_ShowAlert(const BString& text, const BString& label,
1314 	const BString& label2, const BString& label3, alert_type type) const
1315 {
1316 	const char* button2 = NULL;
1317 	if (label2.Length() > 0)
1318 		button2 = label2.String();
1319 
1320 	const char* button3 = NULL;
1321 	button_spacing spacing = B_EVEN_SPACING;
1322 	if (label3.Length() > 0) {
1323 		button3 = label3.String();
1324 		spacing = B_OFFSET_SPACING;
1325 	}
1326 
1327 	BAlert* alert = new BAlert("Alert", text.String(), label.String(), button2,
1328 		button3, B_WIDTH_AS_USUAL, spacing, type);
1329 	alert->SetShortcut(0, B_ESCAPE);
1330 
1331 	return alert->Go();
1332 }
1333