libroot/posix/stdio: Remove unused portions.
[haiku.git] / src / apps / stylededit / StyledEditWindow.cpp
bloba9520742bc0fa90ebc956d23e07c5e0ff67cae82
1 /*
2 * Copyright 2002-2012, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
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
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"
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>
55 using namespace BPrivate;
58 const float kLineViewWidth = 30.0;
59 const char* kInfoAttributeName = "StyledEdit-info";
62 #undef B_TRANSLATION_CONTEXT
63 #define B_TRANSLATION_CONTEXT "StyledEditWindow"
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, ...)
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);
81 // #pragma mark -
84 StyledEditWindow::StyledEditWindow(BRect frame, int32 id, uint32 encoding)
86 BWindow(frame, "untitled", B_DOCUMENT_WINDOW, B_ASYNCHRONOUS_CONTROLS),
87 fFindWindow(NULL),
88 fReplaceWindow(NULL)
90 _InitWindow(encoding);
91 BString unTitled(B_TRANSLATE("Untitled "));
92 unTitled << id;
93 SetTitle(unTitled.String());
94 fSaveItem->SetEnabled(true);
95 // allow saving empty files
96 Show();
100 StyledEditWindow::StyledEditWindow(BRect frame, entry_ref* ref, uint32 encoding)
102 BWindow(frame, "untitled", B_DOCUMENT_WINDOW, B_ASYNCHRONOUS_CONTROLS),
103 fFindWindow(NULL),
104 fReplaceWindow(NULL)
106 _InitWindow(encoding);
107 OpenFile(ref);
108 Show();
112 StyledEditWindow::~StyledEditWindow()
114 delete fSaveMessage;
115 delete fPrintSettings;
116 delete fSavePanel;
120 void
121 StyledEditWindow::Quit()
123 _SwitchNodeMonitor(false);
125 _SaveAttrs();
126 if (StyledEditApp* app = dynamic_cast<StyledEditApp*>(be_app))
127 app->CloseDocument();
128 BWindow::Quit();
132 #undef B_TRANSLATION_CONTEXT
133 #define B_TRANSLATION_CONTEXT "QuitAlert"
136 bool
137 StyledEditWindow::QuitRequested()
139 if (fClean)
140 return true;
142 if (fTextView->TextLength() == 0 && fSaveMessage == NULL)
143 return true;
145 BString alertText;
146 bs_printf(&alertText,
147 B_TRANSLATE("Save changes to the document \"%s\"? "), Title());
149 int32 index = _ShowAlert(alertText, B_TRANSLATE("Cancel"),
150 B_TRANSLATE("Don't save"), B_TRANSLATE("Save"), B_WARNING_ALERT);
152 if (index == 0)
153 return false; // "cancel": dont save, dont close the window
155 if (index == 1)
156 return true; // "don't save": just close the window
158 if (!fSaveMessage) {
159 SaveAs(new BMessage(SAVE_THEN_QUIT));
160 return false;
163 return Save() == B_OK;
167 void
168 StyledEditWindow::MessageReceived(BMessage* message)
170 if (message->WasDropped()) {
171 entry_ref ref;
172 if (message->FindRef("refs", 0, &ref)==B_OK) {
173 message->what = B_REFS_RECEIVED;
174 be_app->PostMessage(message);
178 switch (message->what) {
179 // File menu
180 case MENU_SAVE:
181 if (!fSaveMessage)
182 SaveAs();
183 else
184 Save(fSaveMessage);
185 break;
187 case MENU_SAVEAS:
188 SaveAs();
189 break;
191 case B_SAVE_REQUESTED:
192 Save(message);
193 break;
195 case SAVE_THEN_QUIT:
196 if (Save(message) == B_OK)
197 Quit();
198 break;
200 case MENU_RELOAD:
201 _ReloadDocument(message);
202 break;
204 case MENU_CLOSE:
205 if (QuitRequested())
206 Quit();
207 break;
209 case MENU_PAGESETUP:
210 PageSetup(fTextView->Window()->Title());
211 break;
212 case MENU_PRINT:
213 Print(fTextView->Window()->Title());
214 break;
215 case MENU_QUIT:
216 be_app->PostMessage(B_QUIT_REQUESTED);
217 break;
219 // Edit menu
221 case B_UNDO:
222 ASSERT(fCanUndo || fCanRedo);
223 ASSERT(!(fCanUndo && fCanRedo));
224 if (fCanUndo)
225 fUndoFlag = true;
226 if (fCanRedo)
227 fRedoFlag = true;
229 fTextView->Undo(be_clipboard);
230 break;
231 case B_CUT:
232 fTextView->Cut(be_clipboard);
233 break;
234 case B_COPY:
235 fTextView->Copy(be_clipboard);
236 break;
237 case B_PASTE:
238 fTextView->Paste(be_clipboard);
239 break;
240 case MENU_CLEAR:
241 fTextView->Clear();
242 break;
243 case MENU_FIND:
245 if (fFindWindow == NULL) {
246 BRect findWindowFrame(Frame());
247 findWindowFrame.InsetBy(
248 (findWindowFrame.Width() - 400) / 2,
249 (findWindowFrame.Height() - 235) / 2);
251 fFindWindow = new FindWindow(findWindowFrame, this,
252 &fStringToFind, fCaseSensitive, fWrapAround, fBackSearch);
253 fFindWindow->Show();
255 } else if (fFindWindow->IsHidden())
256 fFindWindow->Show();
257 else
258 fFindWindow->Activate();
259 break;
261 case MSG_FIND_WINDOW_QUIT:
263 fFindWindow = NULL;
264 break;
266 case MSG_REPLACE_WINDOW_QUIT:
268 fReplaceWindow = NULL;
269 break;
271 case MSG_SEARCH:
272 message->FindString("findtext", &fStringToFind);
273 fFindAgainItem->SetEnabled(true);
274 message->FindBool("casesens", &fCaseSensitive);
275 message->FindBool("wrap", &fWrapAround);
276 message->FindBool("backsearch", &fBackSearch);
278 _Search(fStringToFind, fCaseSensitive, fWrapAround, fBackSearch);
279 break;
280 case MENU_FIND_AGAIN:
281 _Search(fStringToFind, fCaseSensitive, fWrapAround, fBackSearch);
282 break;
283 case MENU_FIND_SELECTION:
284 _FindSelection();
285 break;
286 case MENU_REPLACE:
288 if (fReplaceWindow == NULL) {
289 BRect replaceWindowFrame(Frame());
290 replaceWindowFrame.InsetBy(
291 (replaceWindowFrame.Width() - 400) / 2,
292 (replaceWindowFrame.Height() - 284) / 2);
294 fReplaceWindow = new ReplaceWindow(replaceWindowFrame, this,
295 &fStringToFind, &fReplaceString, fCaseSensitive,
296 fWrapAround, fBackSearch);
297 fReplaceWindow->Show();
299 } else if (fReplaceWindow->IsHidden())
300 fReplaceWindow->Show();
301 else
302 fReplaceWindow->Activate();
303 break;
305 case MSG_REPLACE:
307 message->FindBool("casesens", &fCaseSensitive);
308 message->FindBool("wrap", &fWrapAround);
309 message->FindBool("backsearch", &fBackSearch);
311 message->FindString("FindText", &fStringToFind);
312 message->FindString("ReplaceText", &fReplaceString);
314 fFindAgainItem->SetEnabled(true);
315 fReplaceSameItem->SetEnabled(true);
317 _Replace(fStringToFind, fReplaceString, fCaseSensitive, fWrapAround,
318 fBackSearch);
319 break;
321 case MENU_REPLACE_SAME:
322 _Replace(fStringToFind, fReplaceString, fCaseSensitive, fWrapAround,
323 fBackSearch);
324 break;
326 case MSG_REPLACE_ALL:
328 message->FindBool("casesens", &fCaseSensitive);
329 message->FindString("FindText", &fStringToFind);
330 message->FindString("ReplaceText", &fReplaceString);
332 bool allWindows;
333 message->FindBool("allwindows", &allWindows);
335 fFindAgainItem->SetEnabled(true);
336 fReplaceSameItem->SetEnabled(true);
338 if (allWindows)
339 SearchAllWindows(fStringToFind, fReplaceString, fCaseSensitive);
340 else
341 _ReplaceAll(fStringToFind, fReplaceString, fCaseSensitive);
342 break;
345 case B_NODE_MONITOR:
346 _HandleNodeMonitorEvent(message);
347 break;
349 // Font menu
351 case FONT_SIZE:
353 float fontSize;
354 if (message->FindFloat("size", &fontSize) == B_OK)
355 _SetFontSize(fontSize);
356 break;
358 case FONT_FAMILY:
360 const char* fontFamily = NULL;
361 const char* fontStyle = NULL;
362 void* ptr;
363 if (message->FindPointer("source", &ptr) == B_OK) {
364 BMenuItem* item = static_cast<BMenuItem*>(ptr);
365 fontFamily = item->Label();
368 BFont font;
369 font.SetFamilyAndStyle(fontFamily, fontStyle);
370 fItalicItem->SetMarked((font.Face() & B_ITALIC_FACE) != 0);
371 fBoldItem->SetMarked((font.Face() & B_BOLD_FACE) != 0);
373 _SetFontStyle(fontFamily, fontStyle);
374 break;
376 case FONT_STYLE:
378 const char* fontFamily = NULL;
379 const char* fontStyle = NULL;
380 void* ptr;
381 if (message->FindPointer("source", &ptr) == B_OK) {
382 BMenuItem* item = static_cast<BMenuItem*>(ptr);
383 fontStyle = item->Label();
384 BMenu* menu = item->Menu();
385 if (menu != NULL) {
386 BMenuItem* super_item = menu->Superitem();
387 if (super_item != NULL)
388 fontFamily = super_item->Label();
392 BFont font;
393 font.SetFamilyAndStyle(fontFamily, fontStyle);
394 fItalicItem->SetMarked((font.Face() & B_ITALIC_FACE) != 0);
395 fBoldItem->SetMarked((font.Face() & B_BOLD_FACE) != 0);
397 _SetFontStyle(fontFamily, fontStyle);
398 break;
400 case kMsgSetItalic:
402 uint32 sameProperties;
403 BFont font;
404 fTextView->GetFontAndColor(&font, &sameProperties);
406 if (fItalicItem->IsMarked())
407 font.SetFace(B_REGULAR_FACE);
408 fItalicItem->SetMarked(!fItalicItem->IsMarked());
410 font_family family;
411 font_style style;
412 font.GetFamilyAndStyle(&family, &style);
414 _SetFontStyle(family, style);
415 break;
417 case kMsgSetBold:
419 uint32 sameProperties;
420 BFont font;
421 fTextView->GetFontAndColor(&font, &sameProperties);
423 if (fBoldItem->IsMarked())
424 font.SetFace(B_REGULAR_FACE);
425 fBoldItem->SetMarked(!fBoldItem->IsMarked());
427 font_family family;
428 font_style style;
429 font.GetFamilyAndStyle(&family, &style);
431 _SetFontStyle(family, style);
432 break;
434 case FONT_COLOR:
436 ssize_t colorLength;
437 rgb_color* color;
438 if (message->FindData("color", B_RGB_COLOR_TYPE,
439 (const void**)&color, &colorLength) == B_OK
440 && colorLength == sizeof(rgb_color)) {
442 * TODO: Ideally, when selecting the default color,
443 * you wouldn't naively apply it; it shouldn't lose its nature.
444 * When reloaded with a different default color, it should
445 * reflect that different choice.
447 _SetFontColor(color);
449 break;
452 // Document menu
454 case ALIGN_LEFT:
455 fTextView->SetAlignment(B_ALIGN_LEFT);
456 _UpdateCleanUndoRedoSaveRevert();
457 break;
458 case ALIGN_CENTER:
459 fTextView->SetAlignment(B_ALIGN_CENTER);
460 _UpdateCleanUndoRedoSaveRevert();
461 break;
462 case ALIGN_RIGHT:
463 fTextView->SetAlignment(B_ALIGN_RIGHT);
464 _UpdateCleanUndoRedoSaveRevert();
465 break;
466 case WRAP_LINES:
468 BRect textRect(fTextView->Bounds());
469 textRect.OffsetTo(B_ORIGIN);
470 textRect.InsetBy(TEXT_INSET, TEXT_INSET);
471 if (fTextView->DoesWordWrap()) {
472 fTextView->SetWordWrap(false);
473 fWrapItem->SetMarked(false);
474 // the width comes from stylededit R5. TODO: find a better way
475 textRect.SetRightBottom(BPoint(1500.0, textRect.RightBottom().y));
476 } else {
477 fTextView->SetWordWrap(true);
478 fWrapItem->SetMarked(true);
480 fTextView->SetTextRect(textRect);
482 _UpdateCleanUndoRedoSaveRevert();
483 break;
485 case SHOW_STATISTICS:
486 _ShowStatistics();
487 break;
488 case ENABLE_ITEMS:
489 fCutItem->SetEnabled(true);
490 fCopyItem->SetEnabled(true);
491 break;
492 case DISABLE_ITEMS:
493 fCutItem->SetEnabled(false);
494 fCopyItem->SetEnabled(false);
495 break;
496 case TEXT_CHANGED:
497 if (fUndoFlag) {
498 if (fUndoCleans) {
499 // we cleaned!
500 fClean = true;
501 fUndoCleans = false;
502 } else if (fClean) {
503 // if we were clean
504 // then a redo will make us clean again
505 fRedoCleans = true;
506 fClean = false;
508 // set mode
509 fCanUndo = false;
510 fCanRedo = true;
511 fUndoItem->SetLabel(B_TRANSLATE("Redo typing"));
512 fUndoItem->SetEnabled(true);
513 fUndoFlag = false;
514 } else {
515 if (fRedoFlag && fRedoCleans) {
516 // we cleaned!
517 fClean = true;
518 fRedoCleans = false;
519 } else if (fClean) {
520 // if we were clean
521 // then an undo will make us clean again
522 fUndoCleans = true;
523 fClean = false;
524 } else {
525 // no more cleaning from undo now...
526 fUndoCleans = false;
528 // set mode
529 fCanUndo = true;
530 fCanRedo = false;
531 fUndoItem->SetLabel(B_TRANSLATE("Undo typing"));
532 fUndoItem->SetEnabled(true);
533 fRedoFlag = false;
535 if (fClean) {
536 fSaveItem->SetEnabled(fSaveMessage == NULL);
537 } else {
538 fSaveItem->SetEnabled(true);
540 fReloadItem->SetEnabled(fSaveMessage != NULL);
541 fEncodingItem->SetEnabled(fSaveMessage != NULL);
542 break;
544 case SAVE_AS_ENCODING:
545 void* ptr;
546 if (message->FindPointer("source", &ptr) == B_OK
547 && fSavePanelEncodingMenu != NULL) {
548 fTextView->SetEncoding(
549 (uint32)fSavePanelEncodingMenu->IndexOf((BMenuItem*)ptr));
551 break;
553 case UPDATE_STATUS:
555 message->AddBool("modified", !fClean);
556 bool readOnly = !fTextView->IsEditable();
557 message->AddBool("readOnly", readOnly);
558 if (readOnly) {
559 BVolume volume(fNodeRef.device);
560 message->AddBool("canUnlock", !volume.IsReadOnly());
562 fStatusView->SetStatus(message);
563 break;
566 case UPDATE_STATUS_REF:
568 entry_ref ref;
569 const char* name;
571 if (fSaveMessage != NULL
572 && fSaveMessage->FindRef("directory", &ref) == B_OK
573 && fSaveMessage->FindString("name", &name) == B_OK) {
575 BDirectory dir(&ref);
576 status_t status = dir.InitCheck();
577 BEntry entry;
578 if (status == B_OK)
579 status = entry.SetTo(&dir, name);
580 if (status == B_OK)
581 status = entry.GetRef(&ref);
583 fStatusView->SetRef(ref);
584 break;
587 case UNLOCK_FILE:
589 status_t status = _UnlockFile();
590 if (status != B_OK) {
591 BString text;
592 bs_printf(&text,
593 B_TRANSLATE("Unable to unlock file\n\t%s"),
594 strerror(status));
595 _ShowAlert(text, B_TRANSLATE("OK"), "", "", B_STOP_ALERT);
597 PostMessage(UPDATE_STATUS);
598 break;
601 case UPDATE_LINE_SELECTION:
603 int32 line;
604 if (message->FindInt32("be:line", &line) == B_OK) {
605 fTextView->GoToLine(line);
606 fTextView->ScrollToSelection();
609 int32 start, length;
610 if (message->FindInt32("be:selection_offset", &start) == B_OK) {
611 if (message->FindInt32("be:selection_length", &length) != B_OK)
612 length = 0;
614 fTextView->Select(start, start + length);
615 fTextView->ScrollToOffset(start);
617 break;
619 default:
620 BWindow::MessageReceived(message);
621 break;
626 void
627 StyledEditWindow::MenusBeginning()
629 // update the font menu
630 // unselect the old values
631 if (fCurrentFontItem != NULL) {
632 fCurrentFontItem->SetMarked(false);
633 BMenu* menu = fCurrentFontItem->Submenu();
634 if (menu != NULL) {
635 BMenuItem* item = menu->FindMarked();
636 if (item != NULL)
637 item->SetMarked(false);
641 if (fCurrentStyleItem != NULL) {
642 fCurrentStyleItem->SetMarked(false);
645 BMenuItem* oldColorItem = fFontColorMenu->FindMarked();
646 if (oldColorItem != NULL)
647 oldColorItem->SetMarked(false);
649 BMenuItem* oldSizeItem = fFontSizeMenu->FindMarked();
650 if (oldSizeItem != NULL)
651 oldSizeItem->SetMarked(false);
653 // find the current font, color, size
654 BFont font;
655 uint32 sameProperties;
656 rgb_color color = ui_color(B_DOCUMENT_TEXT_COLOR);
657 bool sameColor;
658 fTextView->GetFontAndColor(&font, &sameProperties, &color, &sameColor);
659 color.alpha = 255;
661 if (sameColor) {
662 if (fDefaultFontColorItem->Color() == color)
663 fDefaultFontColorItem->SetMarked(true);
664 else {
665 for (int i = 0; i < fFontColorMenu->CountItems(); i++) {
666 ColorMenuItem* item = dynamic_cast<ColorMenuItem*>
667 (fFontColorMenu->ItemAt(i));
668 if (item != NULL && item->Color() == color) {
669 item->SetMarked(true);
670 break;
676 if (sameProperties & B_FONT_SIZE) {
677 if ((int)font.Size() == font.Size()) {
678 // select the current font size
679 char fontSizeStr[16];
680 snprintf(fontSizeStr, 15, "%i", (int)font.Size());
681 BMenuItem* item = fFontSizeMenu->FindItem(fontSizeStr);
682 if (item != NULL)
683 item->SetMarked(true);
687 font_family family;
688 font_style style;
689 font.GetFamilyAndStyle(&family, &style);
691 fCurrentFontItem = fFontMenu->FindItem(family);
693 if (fCurrentFontItem != NULL) {
694 fCurrentFontItem->SetMarked(true);
695 BMenu* menu = fCurrentFontItem->Submenu();
696 if (menu != NULL) {
697 BMenuItem* item = menu->FindItem(style);
698 fCurrentStyleItem = item;
699 if (fCurrentStyleItem != NULL)
700 item->SetMarked(true);
704 fBoldItem->SetMarked((font.Face() & B_BOLD_FACE) != 0);
705 fItalicItem->SetMarked((font.Face() & B_ITALIC_FACE) != 0);
707 switch (fTextView->Alignment()) {
708 case B_ALIGN_LEFT:
709 default:
710 fAlignLeft->SetMarked(true);
711 break;
712 case B_ALIGN_CENTER:
713 fAlignCenter->SetMarked(true);
714 break;
715 case B_ALIGN_RIGHT:
716 fAlignRight->SetMarked(true);
717 break;
720 // text encoding
721 const BCharacterSet* charset
722 = BCharacterSetRoster::GetCharacterSetByFontID(fTextView->GetEncoding());
723 BMenu* encodingMenu = fEncodingItem->Submenu();
724 if (charset != NULL && encodingMenu != NULL) {
725 const char* mime = charset->GetMIMEName();
726 BString name(charset->GetPrintName());
727 if (mime)
728 name << " (" << mime << ")";
730 BMenuItem* item = encodingMenu->FindItem(name);
731 if (item != NULL)
732 item->SetMarked(true);
737 #undef B_TRANSLATION_CONTEXT
738 #define B_TRANSLATION_CONTEXT "SaveAlert"
741 status_t
742 StyledEditWindow::Save(BMessage* message)
744 _NodeMonitorSuspender nodeMonitorSuspender(this);
746 if (!message)
747 message = fSaveMessage;
749 if (!message)
750 return B_ERROR;
752 entry_ref dirRef;
753 const char* name;
754 if (message->FindRef("directory", &dirRef) != B_OK
755 || message->FindString("name", &name) != B_OK)
756 return B_BAD_VALUE;
758 BDirectory dir(&dirRef);
759 BEntry entry(&dir, name);
761 status_t status = B_ERROR;
762 if (dir.InitCheck() == B_OK && entry.InitCheck() == B_OK) {
763 struct stat st;
764 BFile file(&entry, B_READ_WRITE | B_CREATE_FILE);
765 if (file.InitCheck() == B_OK
766 && (status = file.GetStat(&st)) == B_OK) {
767 // check the file permissions
768 if (!((getuid() == st.st_uid && (S_IWUSR & st.st_mode))
769 || (getgid() == st.st_gid && (S_IWGRP & st.st_mode))
770 || (S_IWOTH & st.st_mode))) {
771 BString alertText;
772 bs_printf(&alertText, B_TRANSLATE("This file is marked "
773 "read-only. Save changes to the document \"%s\"? "), name);
774 switch (_ShowAlert(alertText, B_TRANSLATE("Cancel"),
775 B_TRANSLATE("Don't save"),
776 B_TRANSLATE("Save"), B_WARNING_ALERT)) {
777 case 0:
778 return B_CANCELED;
779 case 1:
780 return B_OK;
781 default:
782 break;
786 status = fTextView->WriteStyledEditFile(&file);
790 if (status != B_OK) {
791 BString alertText;
792 bs_printf(&alertText, B_TRANSLATE("Error saving \"%s\":\n%s"), name,
793 strerror(status));
795 _ShowAlert(alertText, B_TRANSLATE("OK"), "", "", B_STOP_ALERT);
796 return status;
799 SetTitle(name);
801 if (fSaveMessage != message) {
802 delete fSaveMessage;
803 fSaveMessage = new BMessage(*message);
806 // clear clean modes
807 fSaveItem->SetEnabled(false);
808 fUndoCleans = false;
809 fRedoCleans = false;
810 fClean = true;
811 fNagOnNodeChange = true;
813 PostMessage(UPDATE_STATUS);
814 PostMessage(UPDATE_STATUS_REF);
816 return status;
820 #undef B_TRANSLATION_CONTEXT
821 #define B_TRANSLATION_CONTEXT "Open_and_SaveAsPanel"
824 status_t
825 StyledEditWindow::SaveAs(BMessage* message)
827 if (fSavePanel == NULL) {
828 entry_ref* directory = NULL;
829 entry_ref dirRef;
830 if (fSaveMessage != NULL) {
831 if (fSaveMessage->FindRef("directory", &dirRef) == B_OK)
832 directory = &dirRef;
835 BMessenger target(this);
836 fSavePanel = new BFilePanel(B_SAVE_PANEL, &target,
837 directory, B_FILE_NODE, false);
839 BMenuBar* menuBar = dynamic_cast<BMenuBar*>(
840 fSavePanel->Window()->FindView("MenuBar"));
841 if (menuBar != NULL) {
842 fSavePanelEncodingMenu = new BMenu(B_TRANSLATE("Encoding"));
843 fSavePanelEncodingMenu->SetRadioMode(true);
844 menuBar->AddItem(fSavePanelEncodingMenu);
846 BCharacterSetRoster roster;
847 BCharacterSet charset;
848 while (roster.GetNextCharacterSet(&charset) == B_NO_ERROR) {
849 BString name(charset.GetPrintName());
850 const char* mime = charset.GetMIMEName();
851 if (mime) {
852 name.Append(" (");
853 name.Append(mime);
854 name.Append(")");
856 BMenuItem * item = new BMenuItem(name.String(),
857 new BMessage(SAVE_AS_ENCODING));
858 item->SetTarget(this);
859 fSavePanelEncodingMenu->AddItem(item);
860 if (charset.GetFontID() == fTextView->GetEncoding())
861 item->SetMarked(true);
866 fSavePanel->SetSaveText(Title());
867 if (message != NULL)
868 fSavePanel->SetMessage(message);
870 fSavePanel->Show();
871 return B_OK;
875 void
876 StyledEditWindow::OpenFile(entry_ref* ref)
878 if (_LoadFile(ref) != B_OK) {
879 fSaveItem->SetEnabled(true);
880 // allow saving new files
881 return;
884 fSaveMessage = new BMessage(B_SAVE_REQUESTED);
885 if (fSaveMessage) {
886 BEntry entry(ref, true);
887 BEntry parent;
888 entry_ref parentRef;
889 char name[B_FILE_NAME_LENGTH];
891 entry.GetParent(&parent);
892 entry.GetName(name);
893 parent.GetRef(&parentRef);
894 fSaveMessage->AddRef("directory", &parentRef);
895 fSaveMessage->AddString("name", name);
896 SetTitle(name);
898 _LoadAttrs();
901 _SwitchNodeMonitor(true, ref);
903 PostMessage(UPDATE_STATUS_REF);
905 fReloadItem->SetEnabled(fSaveMessage != NULL);
906 fEncodingItem->SetEnabled(fSaveMessage != NULL);
910 status_t
911 StyledEditWindow::PageSetup(const char* documentName)
913 BPrintJob printJob(documentName);
915 if (fPrintSettings != NULL)
916 printJob.SetSettings(new BMessage(*fPrintSettings));
918 status_t result = printJob.ConfigPage();
919 if (result == B_OK) {
920 delete fPrintSettings;
921 fPrintSettings = printJob.Settings();
924 return result;
928 void
929 StyledEditWindow::Print(const char* documentName)
931 BPrintJob printJob(documentName);
932 if (fPrintSettings)
933 printJob.SetSettings(new BMessage(*fPrintSettings));
935 if (printJob.ConfigJob() != B_OK)
936 return;
938 delete fPrintSettings;
939 fPrintSettings = printJob.Settings();
941 // information from printJob
942 BRect printableRect = printJob.PrintableRect();
943 int32 firstPage = printJob.FirstPage();
944 int32 lastPage = printJob.LastPage();
946 // lines eventually to be used to compute pages to print
947 int32 firstLine = 0;
948 int32 lastLine = fTextView->CountLines();
950 // values to be computed
951 int32 pagesInDocument = 1;
952 int32 linesInDocument = fTextView->CountLines();
954 int32 currentLine = 0;
955 while (currentLine < linesInDocument) {
956 float currentHeight = 0;
957 while (currentHeight < printableRect.Height() && currentLine
958 < linesInDocument) {
959 currentHeight += fTextView->LineHeight(currentLine);
960 if (currentHeight < printableRect.Height())
961 currentLine++;
963 if (pagesInDocument == lastPage)
964 lastLine = currentLine - 1;
966 if (currentHeight >= printableRect.Height()) {
967 pagesInDocument++;
968 if (pagesInDocument == firstPage)
969 firstLine = currentLine;
973 if (lastPage > pagesInDocument - 1) {
974 lastPage = pagesInDocument - 1;
975 lastLine = currentLine - 1;
979 printJob.BeginJob();
980 if (fTextView->CountLines() > 0 && fTextView->TextLength() > 0) {
981 int32 printLine = firstLine;
982 while (printLine <= lastLine) {
983 float currentHeight = 0;
984 int32 firstLineOnPage = printLine;
985 while (currentHeight < printableRect.Height()
986 && printLine <= lastLine)
988 currentHeight += fTextView->LineHeight(printLine);
989 if (currentHeight < printableRect.Height())
990 printLine++;
993 float top = 0;
994 if (firstLineOnPage != 0)
995 top = fTextView->TextHeight(0, firstLineOnPage - 1);
997 float bottom = fTextView->TextHeight(0, printLine - 1);
998 BRect textRect(0.0, top + TEXT_INSET,
999 printableRect.Width(), bottom + TEXT_INSET);
1000 printJob.DrawView(fTextView, textRect, B_ORIGIN);
1001 printJob.SpoolPage();
1006 printJob.CommitJob();
1010 void
1011 StyledEditWindow::SearchAllWindows(BString find, BString replace,
1012 bool caseSensitive)
1014 int32 numWindows;
1015 numWindows = be_app->CountWindows();
1017 BMessage* message;
1018 message= new BMessage(MSG_REPLACE_ALL);
1019 message->AddString("FindText", find);
1020 message->AddString("ReplaceText", replace);
1021 message->AddBool("casesens", caseSensitive);
1023 while (numWindows >= 0) {
1024 StyledEditWindow* window = dynamic_cast<StyledEditWindow *>(
1025 be_app->WindowAt(numWindows));
1027 BMessenger messenger(window);
1028 messenger.SendMessage(message);
1030 numWindows--;
1035 bool
1036 StyledEditWindow::IsDocumentEntryRef(const entry_ref* ref)
1038 if (ref == NULL)
1039 return false;
1041 if (fSaveMessage == NULL)
1042 return false;
1044 entry_ref dir;
1045 const char* name;
1046 if (fSaveMessage->FindRef("directory", &dir) != B_OK
1047 || fSaveMessage->FindString("name", &name) != B_OK)
1048 return false;
1050 entry_ref documentRef;
1051 BPath documentPath(&dir);
1052 documentPath.Append(name);
1053 get_ref_for_path(documentPath.Path(), &documentRef);
1055 return *ref == documentRef;
1059 // #pragma mark - private methods
1062 #undef B_TRANSLATION_CONTEXT
1063 #define B_TRANSLATION_CONTEXT "Menus"
1066 void
1067 StyledEditWindow::_InitWindow(uint32 encoding)
1069 fPrintSettings = NULL;
1070 fSaveMessage = NULL;
1072 // undo modes
1073 fUndoFlag = false;
1074 fCanUndo = false;
1075 fRedoFlag = false;
1076 fCanRedo = false;
1078 // clean modes
1079 fUndoCleans = false;
1080 fRedoCleans = false;
1081 fClean = true;
1083 // search- state
1084 fReplaceString = "";
1085 fStringToFind = "";
1086 fCaseSensitive = false;
1087 fWrapAround = false;
1088 fBackSearch = false;
1090 fNagOnNodeChange = true;
1092 // add menubar
1093 fMenuBar = new BMenuBar(BRect(0, 0, 0, 0), "menubar");
1094 AddChild(fMenuBar);
1096 // add textview and scrollview
1098 BRect viewFrame = Bounds();
1099 viewFrame.top = fMenuBar->Bounds().Height() + 1;
1100 viewFrame.right -= B_V_SCROLL_BAR_WIDTH;
1101 viewFrame.left = 0;
1102 viewFrame.bottom -= B_H_SCROLL_BAR_HEIGHT;
1104 BRect textBounds = viewFrame;
1105 textBounds.OffsetTo(B_ORIGIN);
1106 textBounds.InsetBy(TEXT_INSET, TEXT_INSET);
1108 fTextView = new StyledEditView(viewFrame, textBounds, this);
1109 fTextView->SetDoesUndo(true);
1110 fTextView->SetStylable(true);
1111 fTextView->SetEncoding(encoding);
1113 fScrollView = new BScrollView("scrollview", fTextView, B_FOLLOW_ALL, 0,
1114 true, true, B_PLAIN_BORDER);
1115 AddChild(fScrollView);
1116 fTextView->MakeFocus(true);
1118 fStatusView = new StatusView(fScrollView);
1119 fScrollView->AddChild(fStatusView);
1121 // Add "File"-menu:
1122 BMenu* menu = new BMenu(B_TRANSLATE("File"));
1123 fMenuBar->AddItem(menu);
1125 BMenuItem* menuItem;
1126 menu->AddItem(menuItem = new BMenuItem(B_TRANSLATE("New"),
1127 new BMessage(MENU_NEW), 'N'));
1128 menuItem->SetTarget(be_app);
1130 menu->AddItem(menuItem = new BMenuItem(BRecentFilesList::NewFileListMenu(
1131 B_TRANSLATE("Open" B_UTF8_ELLIPSIS), NULL, NULL, be_app, 9, true,
1132 NULL, APP_SIGNATURE), new BMessage(MENU_OPEN)));
1133 menuItem->SetShortcut('O', 0);
1134 menuItem->SetTarget(be_app);
1135 menu->AddSeparatorItem();
1137 menu->AddItem(fSaveItem = new BMenuItem(B_TRANSLATE("Save"),
1138 new BMessage(MENU_SAVE), 'S'));
1139 fSaveItem->SetEnabled(false);
1140 menu->AddItem(menuItem = new BMenuItem(
1141 B_TRANSLATE("Save as" B_UTF8_ELLIPSIS), new BMessage(MENU_SAVEAS)));
1142 menuItem->SetShortcut('S', B_SHIFT_KEY);
1143 menuItem->SetEnabled(true);
1145 menu->AddItem(fReloadItem
1146 = new BMenuItem(B_TRANSLATE("Reload" B_UTF8_ELLIPSIS),
1147 new BMessage(MENU_RELOAD), 'L'));
1148 fReloadItem->SetEnabled(false);
1150 menu->AddItem(new BMenuItem(B_TRANSLATE("Close"),
1151 new BMessage(MENU_CLOSE), 'W'));
1153 menu->AddSeparatorItem();
1154 menu->AddItem(new BMenuItem(B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS),
1155 new BMessage(MENU_PAGESETUP)));
1156 menu->AddItem(new BMenuItem(B_TRANSLATE("Print" B_UTF8_ELLIPSIS),
1157 new BMessage(MENU_PRINT), 'P'));
1159 menu->AddSeparatorItem();
1160 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
1161 new BMessage(MENU_QUIT), 'Q'));
1163 // Add the "Edit"-menu:
1164 menu = new BMenu(B_TRANSLATE("Edit"));
1165 fMenuBar->AddItem(menu);
1167 menu->AddItem(fUndoItem = new BMenuItem(B_TRANSLATE("Can't undo"),
1168 new BMessage(B_UNDO), 'Z'));
1169 fUndoItem->SetEnabled(false);
1171 menu->AddSeparatorItem();
1172 menu->AddItem(fCutItem = new BMenuItem(B_TRANSLATE("Cut"),
1173 new BMessage(B_CUT), 'X'));
1174 fCutItem->SetEnabled(false);
1175 fCutItem->SetTarget(fTextView);
1177 menu->AddItem(fCopyItem = new BMenuItem(B_TRANSLATE("Copy"),
1178 new BMessage(B_COPY), 'C'));
1179 fCopyItem->SetEnabled(false);
1180 fCopyItem->SetTarget(fTextView);
1182 menu->AddItem(menuItem = new BMenuItem(B_TRANSLATE("Paste"),
1183 new BMessage(B_PASTE), 'V'));
1184 menuItem->SetTarget(fTextView);
1186 menu->AddSeparatorItem();
1187 menu->AddItem(menuItem = new BMenuItem(B_TRANSLATE("Select all"),
1188 new BMessage(B_SELECT_ALL), 'A'));
1189 menuItem->SetTarget(fTextView);
1191 menu->AddSeparatorItem();
1192 menu->AddItem(new BMenuItem(B_TRANSLATE("Find" B_UTF8_ELLIPSIS),
1193 new BMessage(MENU_FIND), 'F'));
1194 menu->AddItem(fFindAgainItem= new BMenuItem(B_TRANSLATE("Find again"),
1195 new BMessage(MENU_FIND_AGAIN), 'G'));
1196 fFindAgainItem->SetEnabled(false);
1198 menu->AddItem(new BMenuItem(B_TRANSLATE("Find selection"),
1199 new BMessage(MENU_FIND_SELECTION), 'H'));
1200 menu->AddItem(fReplaceItem = new BMenuItem(B_TRANSLATE("Replace" B_UTF8_ELLIPSIS),
1201 new BMessage(MENU_REPLACE), 'R'));
1202 menu->AddItem(fReplaceSameItem = new BMenuItem(B_TRANSLATE("Replace next"),
1203 new BMessage(MENU_REPLACE_SAME), 'T'));
1204 fReplaceSameItem->SetEnabled(false);
1206 // Add the "Font"-menu:
1207 fFontMenu = new BMenu(B_TRANSLATE("Font"));
1208 fMenuBar->AddItem(fFontMenu);
1210 // "Size"-subMenu
1211 fFontSizeMenu = new BMenu(B_TRANSLATE("Size"));
1212 fFontSizeMenu->SetRadioMode(true);
1213 fFontMenu->AddItem(fFontSizeMenu);
1215 const int32 fontSizes[] = {9, 10, 11, 12, 14, 18, 24, 36, 48, 72};
1216 for (uint32 i = 0; i < sizeof(fontSizes) / sizeof(fontSizes[0]); i++) {
1217 BMessage* fontMessage = new BMessage(FONT_SIZE);
1218 fontMessage->AddFloat("size", fontSizes[i]);
1220 char label[64];
1221 snprintf(label, sizeof(label), "%" B_PRId32, fontSizes[i]);
1222 fFontSizeMenu->AddItem(menuItem = new BMenuItem(label, fontMessage));
1224 if (fontSizes[i] == (int32)be_plain_font->Size())
1225 menuItem->SetMarked(true);
1228 // "Color"-subMenu
1229 fFontColorMenu = new BMenu(B_TRANSLATE("Color"), 0, 0);
1230 fFontColorMenu->SetRadioMode(true);
1231 fFontMenu->AddItem(fFontColorMenu);
1233 _BuildFontColorMenu(fFontColorMenu);
1235 fFontMenu->AddSeparatorItem();
1237 // "Bold" & "Italic" menu items
1238 fFontMenu->AddItem(fBoldItem = new BMenuItem(B_TRANSLATE("Bold"),
1239 new BMessage(kMsgSetBold)));
1240 fFontMenu->AddItem(fItalicItem = new BMenuItem(B_TRANSLATE("Italic"),
1241 new BMessage(kMsgSetItalic)));
1242 fBoldItem->SetShortcut('B', 0);
1243 fItalicItem->SetShortcut('I', 0);
1244 fFontMenu->AddSeparatorItem();
1246 // Available fonts
1248 fCurrentFontItem = 0;
1249 fCurrentStyleItem = 0;
1251 BMenu* subMenu;
1252 int32 numFamilies = count_font_families();
1253 for (int32 i = 0; i < numFamilies; i++) {
1254 font_family family;
1255 if (get_font_family(i, &family) == B_OK) {
1256 subMenu = new BMenu(family);
1257 subMenu->SetRadioMode(true);
1258 fFontMenu->AddItem(new BMenuItem(subMenu,
1259 new BMessage(FONT_FAMILY)));
1261 int32 numStyles = count_font_styles(family);
1262 for (int32 j = 0; j < numStyles; j++) {
1263 font_style style;
1264 uint32 flags;
1265 if (get_font_style(family, j, &style, &flags) == B_OK) {
1266 subMenu->AddItem(new BMenuItem(style,
1267 new BMessage(FONT_STYLE)));
1273 // Add the "Document"-menu:
1274 menu = new BMenu(B_TRANSLATE("Document"));
1275 fMenuBar->AddItem(menu);
1277 // "Align"-subMenu:
1278 subMenu = new BMenu(B_TRANSLATE("Align"));
1279 subMenu->SetRadioMode(true);
1281 subMenu->AddItem(fAlignLeft = new BMenuItem(B_TRANSLATE("Left"),
1282 new BMessage(ALIGN_LEFT)));
1283 fAlignLeft->SetMarked(true);
1284 fAlignLeft->SetShortcut('L', B_OPTION_KEY);
1286 subMenu->AddItem(fAlignCenter = new BMenuItem(B_TRANSLATE("Center"),
1287 new BMessage(ALIGN_CENTER)));
1288 fAlignCenter->SetShortcut('C', B_OPTION_KEY);
1290 subMenu->AddItem(fAlignRight = new BMenuItem(B_TRANSLATE("Right"),
1291 new BMessage(ALIGN_RIGHT)));
1292 fAlignRight->SetShortcut('R', B_OPTION_KEY);
1294 menu->AddItem(subMenu);
1295 menu->AddItem(fWrapItem = new BMenuItem(B_TRANSLATE("Wrap lines"),
1296 new BMessage(WRAP_LINES)));
1297 fWrapItem->SetMarked(true);
1298 fWrapItem->SetShortcut('W', B_OPTION_KEY);
1300 BMessage *message = new BMessage(MENU_RELOAD);
1301 message->AddString("encoding", "auto");
1302 menu->AddItem(fEncodingItem = new BMenuItem(_PopulateEncodingMenu(
1303 new BMenu(B_TRANSLATE("Text encoding")), "UTF-8"),
1304 message));
1305 fEncodingItem->SetEnabled(false);
1307 menu->AddSeparatorItem();
1308 menu->AddItem(new BMenuItem(B_TRANSLATE("Statistics" B_UTF8_ELLIPSIS),
1309 new BMessage(SHOW_STATISTICS)));
1311 fSavePanel = NULL;
1312 fSavePanelEncodingMenu = NULL;
1313 // build lazily
1317 void
1318 StyledEditWindow::_BuildFontColorMenu(BMenu* menu)
1320 if (menu == NULL)
1321 return;
1323 BFont font;
1324 menu->GetFont(&font);
1325 font_height fh;
1326 font.GetHeight(&fh);
1328 const float itemHeight = ceilf(fh.ascent + fh.descent + 2 * fh.leading);
1329 const float margin = 8.0;
1330 const int nbColumns = 5;
1332 BMessage msgTemplate(FONT_COLOR);
1333 BRect matrixArea(0, 0, 0, 0);
1335 // we place the color palette, reserving room at the top
1336 for (uint i = 0; i < sizeof(palette) / sizeof(rgb_color); i++) {
1337 BPoint topLeft((i % nbColumns) * (itemHeight + margin),
1338 (i / nbColumns) * (itemHeight + margin));
1339 BRect buttonArea(topLeft.x, topLeft.y, topLeft.x + itemHeight,
1340 topLeft.y + itemHeight);
1341 buttonArea.OffsetBy(margin, itemHeight + margin + margin);
1342 menu->AddItem(
1343 new ColorMenuItem("", palette[i], new BMessage(msgTemplate)),
1344 buttonArea);
1345 buttonArea.OffsetBy(margin, margin);
1346 matrixArea = matrixArea | buttonArea;
1349 // separator at the bottom to add spacing in the matrix menu
1350 matrixArea.top = matrixArea.bottom;
1351 menu->AddItem(new BSeparatorItem(), matrixArea);
1353 matrixArea.top = 0;
1354 matrixArea.bottom = itemHeight + 4;
1356 BMessage* msg = new BMessage(msgTemplate);
1357 msg->AddBool("default", true);
1358 fDefaultFontColorItem = new ColorMenuItem(B_TRANSLATE("Default"),
1359 ui_color(B_DOCUMENT_TEXT_COLOR), msg);
1360 menu->AddItem(fDefaultFontColorItem, matrixArea);
1362 matrixArea.top = matrixArea.bottom;
1363 matrixArea.bottom = matrixArea.top + margin;
1364 menu->AddItem(new BSeparatorItem(), matrixArea);
1368 void
1369 StyledEditWindow::_LoadAttrs()
1371 entry_ref dir;
1372 const char* name;
1373 if (fSaveMessage->FindRef("directory", &dir) != B_OK
1374 || fSaveMessage->FindString("name", &name) != B_OK)
1375 return;
1377 BPath documentPath(&dir);
1378 documentPath.Append(name);
1380 BNode documentNode(documentPath.Path());
1381 if (documentNode.InitCheck() != B_OK)
1382 return;
1384 // info about position of caret may live in the file attributes
1385 int32 position = 0;
1386 if (documentNode.ReadAttr("be:caret_position", B_INT32_TYPE, 0,
1387 &position, sizeof(position)) != sizeof(position))
1388 position = 0;
1390 fTextView->Select(position, position);
1391 fTextView->ScrollToOffset(position);
1393 BRect newFrame;
1394 ssize_t bytesRead = documentNode.ReadAttr(kInfoAttributeName, B_RECT_TYPE,
1395 0, &newFrame, sizeof(BRect));
1396 if (bytesRead != sizeof(BRect))
1397 return;
1399 swap_data(B_RECT_TYPE, &newFrame, sizeof(BRect), B_SWAP_BENDIAN_TO_HOST);
1401 // Check if the frame in on screen, otherwise, ignore it
1402 BScreen screen(this);
1403 if (newFrame.Width() > 32 && newFrame.Height() > 32
1404 && screen.Frame().Contains(newFrame)) {
1405 MoveTo(newFrame.left, newFrame.top);
1406 ResizeTo(newFrame.Width(), newFrame.Height());
1411 void
1412 StyledEditWindow::_SaveAttrs()
1414 if (!fSaveMessage)
1415 return;
1417 entry_ref dir;
1418 const char* name;
1419 if (fSaveMessage->FindRef("directory", &dir) != B_OK
1420 || fSaveMessage->FindString("name", &name) != B_OK)
1421 return;
1423 BPath documentPath(&dir);
1424 documentPath.Append(name);
1426 BNode documentNode(documentPath.Path());
1427 if (documentNode.InitCheck() != B_OK)
1428 return;
1430 BRect frame(Frame());
1431 swap_data(B_RECT_TYPE, &frame, sizeof(BRect), B_SWAP_HOST_TO_BENDIAN);
1433 documentNode.WriteAttr(kInfoAttributeName, B_RECT_TYPE, 0, &frame,
1434 sizeof(BRect));
1436 // preserve caret line and position
1437 int32 start, end;
1438 fTextView->GetSelection(&start, &end);
1439 documentNode.WriteAttr("be:caret_position",
1440 B_INT32_TYPE, 0, &start, sizeof(start));
1444 #undef B_TRANSLATION_CONTEXT
1445 #define B_TRANSLATION_CONTEXT "LoadAlert"
1448 status_t
1449 StyledEditWindow::_LoadFile(entry_ref* ref, const char* forceEncoding)
1451 BEntry entry(ref, true);
1452 // traverse an eventual link
1454 status_t status = entry.InitCheck();
1455 if (status == B_OK && entry.IsDirectory())
1456 status = B_IS_A_DIRECTORY;
1458 BFile file;
1459 if (status == B_OK)
1460 status = file.SetTo(&entry, B_READ_ONLY);
1461 if (status == B_OK)
1462 status = fTextView->GetStyledText(&file, forceEncoding);
1464 if (status == B_ENTRY_NOT_FOUND) {
1465 // Treat non-existing files consideratley; we just want to get an
1466 // empty window for them - to create this new document
1467 status = B_OK;
1470 if (status != B_OK) {
1471 // If an error occured, bail out and tell the user what happened
1472 BEntry entry(ref, true);
1473 char name[B_FILE_NAME_LENGTH];
1474 if (entry.GetName(name) != B_OK)
1475 strlcpy(name, B_TRANSLATE("???"), sizeof(name));
1477 BString text;
1478 if (status == B_BAD_TYPE)
1479 bs_printf(&text,
1480 B_TRANSLATE("Error loading \"%s\":\n\tUnsupported format"), name);
1481 else
1482 bs_printf(&text, B_TRANSLATE("Error loading \"%s\":\n\t%s"),
1483 name, strerror(status));
1485 _ShowAlert(text, B_TRANSLATE("OK"), "", "", B_STOP_ALERT);
1486 return status;
1489 struct stat st;
1490 if (file.InitCheck() == B_OK && file.GetStat(&st) == B_OK) {
1491 bool editable = (getuid() == st.st_uid && S_IWUSR & st.st_mode)
1492 || (getgid() == st.st_gid && S_IWGRP & st.st_mode)
1493 || (S_IWOTH & st.st_mode);
1494 BVolume volume(ref->device);
1495 editable = editable && !volume.IsReadOnly();
1496 _SetReadOnly(!editable);
1499 // update alignment
1500 switch (fTextView->Alignment()) {
1501 case B_ALIGN_LEFT:
1502 default:
1503 fAlignLeft->SetMarked(true);
1504 break;
1505 case B_ALIGN_CENTER:
1506 fAlignCenter->SetMarked(true);
1507 break;
1508 case B_ALIGN_RIGHT:
1509 fAlignRight->SetMarked(true);
1510 break;
1513 // update word wrapping
1514 fWrapItem->SetMarked(fTextView->DoesWordWrap());
1515 return B_OK;
1519 #undef B_TRANSLATION_CONTEXT
1520 #define B_TRANSLATION_CONTEXT "RevertToSavedAlert"
1523 void
1524 StyledEditWindow::_ReloadDocument(BMessage* message)
1526 entry_ref ref;
1527 const char* name;
1529 if (fSaveMessage == NULL || message == NULL
1530 || fSaveMessage->FindRef("directory", &ref) != B_OK
1531 || fSaveMessage->FindString("name", &name) != B_OK)
1532 return;
1534 BDirectory dir(&ref);
1535 status_t status = dir.InitCheck();
1536 BEntry entry;
1537 if (status == B_OK)
1538 status = entry.SetTo(&dir, name);
1540 if (status == B_OK)
1541 status = entry.GetRef(&ref);
1543 if (status != B_OK || !entry.Exists()) {
1544 BString alertText;
1545 bs_printf(&alertText,
1546 B_TRANSLATE("Cannot revert, file not found: \"%s\"."), name);
1547 _ShowAlert(alertText, B_TRANSLATE("OK"), "", "", B_STOP_ALERT);
1548 return;
1551 if (!fClean) {
1552 BString alertText;
1553 bs_printf(&alertText,
1554 B_TRANSLATE("\"%s\" has unsaved changes.\n"
1555 "Revert it to the last saved version? "), Title());
1556 if (_ShowAlert(alertText, B_TRANSLATE("Cancel"), B_TRANSLATE("Revert"),
1557 "", B_WARNING_ALERT) != 1)
1558 return;
1561 const BCharacterSet* charset
1562 = BCharacterSetRoster::GetCharacterSetByFontID(
1563 fTextView->GetEncoding());
1564 const char* forceEncoding = NULL;
1565 if (message->FindString("encoding", &forceEncoding) != B_OK) {
1566 if (charset != NULL)
1567 forceEncoding = charset->GetName();
1568 } else {
1569 if (charset != NULL) {
1570 // UTF8 id assumed equal to -1
1571 const uint32 idUTF8 = (uint32)-1;
1572 uint32 id = charset->GetConversionID();
1573 if (strcmp(forceEncoding, "next") == 0)
1574 id = id == B_MS_WINDOWS_1250_CONVERSION ? idUTF8 : id + 1;
1575 else if (strcmp(forceEncoding, "previous") == 0)
1576 id = id == idUTF8 ? B_MS_WINDOWS_1250_CONVERSION : id - 1;
1577 const BCharacterSet* newCharset
1578 = BCharacterSetRoster::GetCharacterSetByConversionID(id);
1579 if (newCharset != NULL)
1580 forceEncoding = newCharset->GetName();
1584 BScrollBar* vertBar = fScrollView->ScrollBar(B_VERTICAL);
1585 float vertPos = vertBar != NULL ? vertBar->Value() : 0.f;
1587 DisableUpdates();
1589 fTextView->Reset();
1591 status = _LoadFile(&ref, forceEncoding);
1593 if (vertBar != NULL)
1594 vertBar->SetValue(vertPos);
1596 EnableUpdates();
1598 if (status != B_OK)
1599 return;
1601 #undef B_TRANSLATION_CONTEXT
1602 #define B_TRANSLATION_CONTEXT "Menus"
1604 // clear undo modes
1605 fUndoItem->SetLabel(B_TRANSLATE("Can't undo"));
1606 fUndoItem->SetEnabled(false);
1607 fUndoFlag = false;
1608 fCanUndo = false;
1609 fRedoFlag = false;
1610 fCanRedo = false;
1612 // clear clean modes
1613 fSaveItem->SetEnabled(false);
1615 fUndoCleans = false;
1616 fRedoCleans = false;
1617 fClean = true;
1619 fNagOnNodeChange = true;
1623 status_t
1624 StyledEditWindow::_UnlockFile()
1626 _NodeMonitorSuspender nodeMonitorSuspender(this);
1628 if (!fSaveMessage)
1629 return B_ERROR;
1631 entry_ref dirRef;
1632 const char* name;
1633 if (fSaveMessage->FindRef("directory", &dirRef) != B_OK
1634 || fSaveMessage->FindString("name", &name) != B_OK)
1635 return B_BAD_VALUE;
1637 BDirectory dir(&dirRef);
1638 BEntry entry(&dir, name);
1640 status_t status = dir.InitCheck();
1641 if (status != B_OK)
1642 return status;
1644 status = entry.InitCheck();
1645 if (status != B_OK)
1646 return status;
1648 struct stat st;
1649 BFile file(&entry, B_READ_WRITE);
1650 status = file.InitCheck();
1651 if (status != B_OK)
1652 return status;
1654 status = file.GetStat(&st);
1655 if (status != B_OK)
1656 return status;
1658 st.st_mode |= S_IWUSR;
1659 status = file.SetPermissions(st.st_mode);
1660 if (status == B_OK)
1661 _SetReadOnly(false);
1663 return status;
1667 bool
1668 StyledEditWindow::_Search(BString string, bool caseSensitive, bool wrap,
1669 bool backSearch, bool scrollToOccurence)
1671 int32 start;
1672 int32 finish;
1674 start = B_ERROR;
1676 int32 length = string.Length();
1677 if (length == 0)
1678 return false;
1680 BString viewText(fTextView->Text());
1681 int32 textStart, textFinish;
1682 fTextView->GetSelection(&textStart, &textFinish);
1683 if (backSearch) {
1684 if (caseSensitive)
1685 start = viewText.FindLast(string, textStart);
1686 else
1687 start = viewText.IFindLast(string, textStart);
1688 } else {
1689 if (caseSensitive)
1690 start = viewText.FindFirst(string, textFinish);
1691 else
1692 start = viewText.IFindFirst(string, textFinish);
1694 if (start == B_ERROR && wrap) {
1695 if (backSearch) {
1696 if (caseSensitive)
1697 start = viewText.FindLast(string, viewText.Length());
1698 else
1699 start = viewText.IFindLast(string, viewText.Length());
1700 } else {
1701 if (caseSensitive)
1702 start = viewText.FindFirst(string, 0);
1703 else
1704 start = viewText.IFindFirst(string, 0);
1708 if (start != B_ERROR) {
1709 finish = start + length;
1710 fTextView->Select(start, finish);
1712 if (scrollToOccurence)
1713 fTextView->ScrollToSelection();
1714 return true;
1717 return false;
1721 void
1722 StyledEditWindow::_FindSelection()
1724 int32 selectionStart, selectionFinish;
1725 fTextView->GetSelection(&selectionStart, &selectionFinish);
1727 int32 selectionLength = selectionFinish- selectionStart;
1729 BString viewText = fTextView->Text();
1730 viewText.CopyInto(fStringToFind, selectionStart, selectionLength);
1731 fFindAgainItem->SetEnabled(true);
1732 _Search(fStringToFind, fCaseSensitive, fWrapAround, fBackSearch);
1736 bool
1737 StyledEditWindow::_Replace(BString findThis, BString replaceWith,
1738 bool caseSensitive, bool wrap, bool backSearch)
1740 if (_Search(findThis, caseSensitive, wrap, backSearch)) {
1741 int32 start;
1742 int32 finish;
1743 fTextView->GetSelection(&start, &finish);
1745 _UpdateCleanUndoRedoSaveRevert();
1746 fTextView->SetSuppressChanges(true);
1747 fTextView->Delete(start, start + findThis.Length());
1748 fTextView->Insert(start, replaceWith.String(), replaceWith.Length());
1749 fTextView->SetSuppressChanges(false);
1750 fTextView->Select(start, start + replaceWith.Length());
1751 fTextView->ScrollToSelection();
1752 return true;
1755 return false;
1759 void
1760 StyledEditWindow::_ReplaceAll(BString findThis, BString replaceWith,
1761 bool caseSensitive)
1763 bool first = true;
1764 fTextView->SetSuppressChanges(true);
1766 // start from the beginning of text
1767 fTextView->Select(0, 0);
1769 // iterate occurences of findThis without wrapping around
1770 while (_Search(findThis, caseSensitive, false, false, false)) {
1771 if (first) {
1772 _UpdateCleanUndoRedoSaveRevert();
1773 first = false;
1775 int32 start;
1776 int32 finish;
1778 fTextView->GetSelection(&start, &finish);
1779 fTextView->Delete(start, start + findThis.Length());
1780 fTextView->Insert(start, replaceWith.String(), replaceWith.Length());
1782 // advance the caret behind the inserted text
1783 start += replaceWith.Length();
1784 fTextView->Select(start, start);
1786 fTextView->ScrollToSelection();
1787 fTextView->SetSuppressChanges(false);
1791 void
1792 StyledEditWindow::_SetFontSize(float fontSize)
1794 uint32 sameProperties;
1795 BFont font;
1797 fTextView->GetFontAndColor(&font, &sameProperties);
1798 font.SetSize(fontSize);
1799 fTextView->SetFontAndColor(&font, B_FONT_SIZE);
1801 _UpdateCleanUndoRedoSaveRevert();
1805 void
1806 StyledEditWindow::_SetFontColor(const rgb_color* color)
1808 uint32 sameProperties;
1809 BFont font;
1811 fTextView->GetFontAndColor(&font, &sameProperties, NULL, NULL);
1812 fTextView->SetFontAndColor(&font, 0, color);
1814 _UpdateCleanUndoRedoSaveRevert();
1818 void
1819 StyledEditWindow::_SetFontStyle(const char* fontFamily, const char* fontStyle)
1821 BFont font;
1822 uint32 sameProperties;
1824 // find out what the old font was
1825 font_family oldFamily;
1826 font_style oldStyle;
1827 fTextView->GetFontAndColor(&font, &sameProperties);
1828 font.GetFamilyAndStyle(&oldFamily, &oldStyle);
1830 // clear that family's bit on the menu, if necessary
1831 if (strcmp(oldFamily, fontFamily)) {
1832 BMenuItem* oldItem = fFontMenu->FindItem(oldFamily);
1833 if (oldItem != NULL) {
1834 oldItem->SetMarked(false);
1835 BMenu* menu = oldItem->Submenu();
1836 if (menu != NULL) {
1837 oldItem = menu->FindItem(oldStyle);
1838 if (oldItem != NULL)
1839 oldItem->SetMarked(false);
1844 font.SetFamilyAndStyle(fontFamily, fontStyle);
1846 uint16 face = 0;
1848 if (!(font.Face() & B_REGULAR_FACE))
1849 face = font.Face();
1851 if (fBoldItem->IsMarked())
1852 face |= B_BOLD_FACE;
1854 if (fItalicItem->IsMarked())
1855 face |= B_ITALIC_FACE;
1857 font.SetFace(face);
1859 fTextView->SetFontAndColor(&font, B_FONT_FAMILY_AND_STYLE);
1861 BMenuItem* superItem;
1862 superItem = fFontMenu->FindItem(fontFamily);
1863 if (superItem != NULL) {
1864 superItem->SetMarked(true);
1865 fCurrentFontItem = superItem;
1868 _UpdateCleanUndoRedoSaveRevert();
1872 #undef B_TRANSLATION_CONTEXT
1873 #define B_TRANSLATION_CONTEXT "Statistics"
1876 int32
1877 StyledEditWindow::_ShowStatistics()
1879 size_t words = 0;
1880 bool inWord = false;
1881 size_t length = fTextView->TextLength();
1883 for (size_t i = 0; i < length; i++) {
1884 if (BUnicodeChar::IsWhitespace(fTextView->Text()[i])) {
1885 inWord = false;
1886 } else if (!inWord) {
1887 words++;
1888 inWord = true;
1892 BString result;
1893 result << B_TRANSLATE("Document statistics") << '\n' << '\n'
1894 << B_TRANSLATE("Lines:") << ' ' << fTextView->CountLines() << '\n'
1895 << B_TRANSLATE("Characters:") << ' ' << length << '\n'
1896 << B_TRANSLATE("Words:") << ' ' << words;
1898 BAlert* alert = new BAlert("Statistics", result, B_TRANSLATE("OK"), NULL,
1899 NULL, B_WIDTH_AS_USUAL, B_EVEN_SPACING, B_INFO_ALERT);
1900 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1902 return alert->Go();
1906 void
1907 StyledEditWindow::_SetReadOnly(bool readOnly)
1909 fReplaceItem->SetEnabled(!readOnly);
1910 fReplaceSameItem->SetEnabled(!readOnly);
1911 fFontMenu->SetEnabled(!readOnly);
1912 fAlignLeft->Menu()->SetEnabled(!readOnly);
1913 fWrapItem->SetEnabled(!readOnly);
1914 fTextView->MakeEditable(!readOnly);
1918 #undef B_TRANSLATION_CONTEXT
1919 #define B_TRANSLATION_CONTEXT "Menus"
1922 void
1923 StyledEditWindow::_UpdateCleanUndoRedoSaveRevert()
1925 fClean = false;
1926 fUndoCleans = false;
1927 fRedoCleans = false;
1928 fReloadItem->SetEnabled(fSaveMessage != NULL);
1929 fEncodingItem->SetEnabled(fSaveMessage != NULL);
1930 fSaveItem->SetEnabled(true);
1931 fUndoItem->SetLabel(B_TRANSLATE("Can't undo"));
1932 fUndoItem->SetEnabled(false);
1933 fCanUndo = false;
1934 fCanRedo = false;
1938 int32
1939 StyledEditWindow::_ShowAlert(const BString& text, const BString& label,
1940 const BString& label2, const BString& label3, alert_type type) const
1942 const char* button2 = NULL;
1943 if (label2.Length() > 0)
1944 button2 = label2.String();
1946 const char* button3 = NULL;
1947 button_spacing spacing = B_EVEN_SPACING;
1948 if (label3.Length() > 0) {
1949 button3 = label3.String();
1950 spacing = B_OFFSET_SPACING;
1953 BAlert* alert = new BAlert("Alert", text.String(), label.String(), button2,
1954 button3, B_WIDTH_AS_USUAL, spacing, type);
1955 alert->SetShortcut(0, B_ESCAPE);
1957 return alert->Go();
1961 BMenu*
1962 StyledEditWindow::_PopulateEncodingMenu(BMenu* menu, const char* currentEncoding)
1964 menu->SetRadioMode(true);
1965 BString encoding(currentEncoding);
1966 if (encoding.Length() == 0)
1967 encoding.SetTo("UTF-8");
1969 BCharacterSetRoster roster;
1970 BCharacterSet charset;
1971 while (roster.GetNextCharacterSet(&charset) == B_OK) {
1972 const char* mime = charset.GetMIMEName();
1973 BString name(charset.GetPrintName());
1975 if (mime)
1976 name << " (" << mime << ")";
1978 BMessage *message = new BMessage(MENU_RELOAD);
1979 if (message != NULL) {
1980 message->AddString("encoding", charset.GetName());
1981 BMenuItem* item = new BMenuItem(name, message);
1982 if (encoding.Compare(charset.GetName()) == 0)
1983 item->SetMarked(true);
1984 menu->AddItem(item);
1988 menu->AddSeparatorItem();
1989 BMessage *message = new BMessage(MENU_RELOAD);
1990 message->AddString("encoding", "auto");
1991 menu->AddItem(new BMenuItem(B_TRANSLATE("Autodetect"), message));
1993 message = new BMessage(MENU_RELOAD);
1994 message->AddString("encoding", "next");
1995 AddShortcut(B_PAGE_DOWN, B_OPTION_KEY, message);
1996 message = new BMessage(MENU_RELOAD);
1997 message->AddString("encoding", "previous");
1998 AddShortcut(B_PAGE_UP, B_OPTION_KEY, message);
2000 return menu;
2004 #undef B_TRANSLATION_CONTEXT
2005 #define B_TRANSLATION_CONTEXT "NodeMonitorAlerts"
2008 void
2009 StyledEditWindow::_ShowNodeChangeAlert(const char* name, bool removed)
2011 if (!fNagOnNodeChange)
2012 return;
2014 BString alertText(removed ? B_TRANSLATE("File \"%file%\" was removed by "
2015 "another application, recover it?")
2016 : B_TRANSLATE("File \"%file%\" was modified by "
2017 "another application, reload it?"));
2018 alertText.ReplaceAll("%file%", name);
2020 if (_ShowAlert(alertText, removed ? B_TRANSLATE("Recover")
2021 : B_TRANSLATE("Reload"), B_TRANSLATE("Ignore"), "",
2022 B_WARNING_ALERT) == 0)
2024 if (!removed) {
2025 // supress the warning - user has already agreed
2026 fClean = true;
2027 BMessage msg(MENU_RELOAD);
2028 _ReloadDocument(&msg);
2029 } else
2030 Save();
2031 } else
2032 fNagOnNodeChange = false;
2034 fSaveItem->SetEnabled(!fClean);
2038 void
2039 StyledEditWindow::_HandleNodeMonitorEvent(BMessage *message)
2041 int32 opcode = 0;
2042 if (message->FindInt32("opcode", &opcode) != B_OK)
2043 return;
2045 if (opcode != B_ENTRY_CREATED
2046 && message->FindInt64("node") != fNodeRef.node)
2047 // bypass foreign nodes' event
2048 return;
2050 switch (opcode) {
2051 case B_STAT_CHANGED:
2053 int32 fields = 0;
2054 if (message->FindInt32("fields", &fields) == B_OK
2055 && (fields & (B_STAT_SIZE | B_STAT_MODIFICATION_TIME
2056 | B_STAT_MODE)) == 0)
2057 break;
2059 const char* name = NULL;
2060 if (fSaveMessage->FindString("name", &name) != B_OK)
2061 break;
2063 _ShowNodeChangeAlert(name, false);
2065 break;
2067 case B_ENTRY_MOVED:
2069 int32 device = 0;
2070 int64 srcFolder = 0;
2071 int64 dstFolder = 0;
2072 const char* name = NULL;
2073 if (message->FindInt32("device", &device) != B_OK
2074 || message->FindInt64("to directory", &dstFolder) != B_OK
2075 || message->FindInt64("from directory", &srcFolder) != B_OK
2076 || message->FindString("name", &name) != B_OK)
2077 break;
2079 entry_ref newRef(device, dstFolder, name);
2080 BEntry entry(&newRef);
2082 BEntry dirEntry;
2083 entry.GetParent(&dirEntry);
2085 entry_ref ref;
2086 dirEntry.GetRef(&ref);
2087 fSaveMessage->ReplaceRef("directory", &ref);
2088 fSaveMessage->ReplaceString("name", name);
2090 // store previous name - it may be useful in case
2091 // we have just moved to temporary copy of file (vim case)
2092 const char* sourceName = NULL;
2093 if (message->FindString("from name", &sourceName) == B_OK) {
2094 fSaveMessage->RemoveName("org.name");
2095 fSaveMessage->AddString("org.name", sourceName);
2096 fSaveMessage->RemoveName("move time");
2097 fSaveMessage->AddInt64("move time", system_time());
2100 SetTitle(name);
2102 if (srcFolder != dstFolder) {
2103 _SwitchNodeMonitor(false);
2104 _SwitchNodeMonitor(true);
2106 PostMessage(UPDATE_STATUS_REF);
2108 break;
2110 case B_ENTRY_REMOVED:
2112 _SwitchNodeMonitor(false);
2114 fClean = false;
2116 // some editors like vim save files in following way:
2117 // 1) move t.txt -> t.txt~
2118 // 2) re-create t.txt and write data to it
2119 // 3) remove t.txt~
2120 // go to catch this case
2121 int32 device = 0;
2122 int64 directory = 0;
2123 BString orgName;
2124 if (fSaveMessage->FindString("org.name", &orgName) == B_OK
2125 && message->FindInt32("device", &device) == B_OK
2126 && message->FindInt64("directory", &directory) == B_OK)
2128 // reuse the source name if it is not too old
2129 bigtime_t time = fSaveMessage->FindInt64("move time");
2130 if ((system_time() - time) < 1000000) {
2131 entry_ref ref(device, directory, orgName);
2132 BEntry entry(&ref);
2133 if (entry.InitCheck() == B_OK) {
2134 _SwitchNodeMonitor(true, &ref);
2137 fSaveMessage->ReplaceString("name", orgName);
2138 fSaveMessage->RemoveName("org.name");
2139 fSaveMessage->RemoveName("move time");
2141 SetTitle(orgName);
2142 _ShowNodeChangeAlert(orgName, false);
2143 break;
2147 const char* name = NULL;
2148 if (message->FindString("name", &name) != B_OK
2149 && fSaveMessage->FindString("name", &name) != B_OK)
2150 name = "Unknown";
2152 _ShowNodeChangeAlert(name, true);
2153 PostMessage(UPDATE_STATUS_REF);
2155 break;
2157 default:
2158 break;
2163 void
2164 StyledEditWindow::_SwitchNodeMonitor(bool on, entry_ref* ref)
2166 if (!on) {
2167 watch_node(&fNodeRef, B_STOP_WATCHING, this);
2168 watch_node(&fFolderNodeRef, B_STOP_WATCHING, this);
2169 fNodeRef = node_ref();
2170 fFolderNodeRef = node_ref();
2171 return;
2174 BEntry entry, folderEntry;
2176 if (ref != NULL) {
2177 entry.SetTo(ref, true);
2178 entry.GetParent(&folderEntry);
2180 } else if (fSaveMessage != NULL) {
2181 entry_ref ref;
2182 const char* name = NULL;
2183 if (fSaveMessage->FindRef("directory", &ref) != B_OK
2184 || fSaveMessage->FindString("name", &name) != B_OK)
2185 return;
2187 BDirectory dir(&ref);
2188 entry.SetTo(&dir, name);
2189 folderEntry.SetTo(&ref);
2191 } else
2192 return;
2194 if (entry.InitCheck() != B_OK || folderEntry.InitCheck() != B_OK)
2195 return;
2197 entry.GetNodeRef(&fNodeRef);
2198 folderEntry.GetNodeRef(&fFolderNodeRef);
2200 watch_node(&fNodeRef, B_WATCH_STAT, this);
2201 watch_node(&fFolderNodeRef, B_WATCH_DIRECTORY, this);