/* * Copyright 2002-2020, Haiku, Inc. All Rights Reserved. * Distributed under the terms of the MIT License. * * Authors: * Mattias Sundblad * Andrew Bachmann * Philippe Saint-Pierre * Jonas Sundström * Ryan Leavengood * Vlad Slepukhin * Sarzhuk Zharski * Pascal R. G. Abresch */ #include "ColorMenuItem.h" #include "Constants.h" #include "FindWindow.h" #include "ReplaceWindow.h" #include "StatusView.h" #include "StyledEditApp.h" #include "StyledEditView.h" #include "StyledEditWindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace BPrivate; const float kLineViewWidth = 30.0; const char* kInfoAttributeName = "StyledEdit-info"; #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "StyledEditWindow" // This is a temporary solution for building BString with printf like format. // will be removed in the future. static void bs_printf(BString* string, const char* format, ...) { va_list ap; va_start(ap, format); char* buf; vasprintf(&buf, format, ap); string->SetTo(buf); free(buf); va_end(ap); } // #pragma mark - StyledEditWindow::StyledEditWindow(BRect frame, int32 id, uint32 encoding) : BWindow(frame, "untitled", B_DOCUMENT_WINDOW, B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS), fFindWindow(NULL), fReplaceWindow(NULL) { _InitWindow(encoding); BString unTitled(B_TRANSLATE("Untitled ")); unTitled << id; SetTitle(unTitled.String()); fSaveItem->SetEnabled(true); // allow saving empty files Show(); } StyledEditWindow::StyledEditWindow(BRect frame, entry_ref* ref, uint32 encoding) : BWindow(frame, "untitled", B_DOCUMENT_WINDOW, B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS), fFindWindow(NULL), fReplaceWindow(NULL) { _InitWindow(encoding); OpenFile(ref); Show(); } StyledEditWindow::~StyledEditWindow() { delete fSaveMessage; delete fPrintSettings; delete fSavePanel; } void StyledEditWindow::Quit() { _SwitchNodeMonitor(false); _SaveAttrs(); if (StyledEditApp* app = dynamic_cast(be_app)) app->CloseDocument(); BWindow::Quit(); } #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "QuitAlert" bool StyledEditWindow::QuitRequested() { if (fClean) return true; if (fTextView->TextLength() == 0 && fSaveMessage == NULL) return true; BString alertText; bs_printf(&alertText, B_TRANSLATE("Save changes to the document \"%s\"? "), Title()); int32 index = _ShowAlert(alertText, B_TRANSLATE("Cancel"), B_TRANSLATE("Don't save"), B_TRANSLATE("Save"), B_WARNING_ALERT); if (index == 0) return false; // "cancel": dont save, dont close the window if (index == 1) return true; // "don't save": just close the window if (!fSaveMessage) { SaveAs(new BMessage(SAVE_THEN_QUIT)); return false; } return Save() == B_OK; } void StyledEditWindow::MessageReceived(BMessage* message) { if (message->WasDropped()) { entry_ref ref; if (message->FindRef("refs", 0, &ref)==B_OK) { message->what = B_REFS_RECEIVED; be_app->PostMessage(message); } } switch (message->what) { case MENU_NEW: // this is because of the layout menu change, // it is too early to connect to a different looper there // so either have to redirect it here, or change the invocation be_app->PostMessage(message); break; // File menu case MENU_SAVE: if (!fSaveMessage) SaveAs(); else Save(fSaveMessage); break; case MENU_SAVEAS: SaveAs(); break; case B_SAVE_REQUESTED: Save(message); break; case SAVE_THEN_QUIT: if (Save(message) == B_OK) Quit(); break; case MENU_RELOAD: _ReloadDocument(message); break; case MENU_CLOSE: if (QuitRequested()) Quit(); break; case MENU_PAGESETUP: PageSetup(fTextView->Window()->Title()); break; case MENU_PRINT: Print(fTextView->Window()->Title()); break; case MENU_QUIT: be_app->PostMessage(B_QUIT_REQUESTED); break; // Edit menu case B_UNDO: ASSERT(fCanUndo || fCanRedo); ASSERT(!(fCanUndo && fCanRedo)); if (fCanUndo) fUndoFlag = true; if (fCanRedo) fRedoFlag = true; fTextView->Undo(be_clipboard); break; case B_CUT: case B_COPY: case B_PASTE: case B_SELECT_ALL: fTextView->MessageReceived(message); break; case MENU_CLEAR: fTextView->Clear(); break; case MENU_FIND: { if (fFindWindow == NULL) { BRect findWindowFrame(Frame()); findWindowFrame.InsetBy( (findWindowFrame.Width() - 400) / 2, (findWindowFrame.Height() - 235) / 2); fFindWindow = new FindWindow(findWindowFrame, this, &fStringToFind, fCaseSensitive, fWrapAround, fBackSearch); fFindWindow->Show(); } else if (fFindWindow->IsHidden()) fFindWindow->Show(); else fFindWindow->Activate(); break; } case MSG_FIND_WINDOW_QUIT: { fFindWindow = NULL; Activate(); // In case any 'always on top' application tries to make its // window active after fFindWindow is closed. break; } case MSG_REPLACE_WINDOW_QUIT: { fReplaceWindow = NULL; Activate(); break; } case MSG_SEARCH: message->FindString("findtext", &fStringToFind); fFindAgainItem->SetEnabled(true); message->FindBool("casesens", &fCaseSensitive); message->FindBool("wrap", &fWrapAround); message->FindBool("backsearch", &fBackSearch); _Search(fStringToFind, fCaseSensitive, fWrapAround, fBackSearch); Activate(); break; case MENU_FIND_AGAIN: _Search(fStringToFind, fCaseSensitive, fWrapAround, fBackSearch); break; case MENU_FIND_SELECTION: _FindSelection(); break; case MENU_REPLACE: { if (fReplaceWindow == NULL) { BRect replaceWindowFrame(Frame()); replaceWindowFrame.InsetBy( (replaceWindowFrame.Width() - 400) / 2, (replaceWindowFrame.Height() - 284) / 2); fReplaceWindow = new ReplaceWindow(replaceWindowFrame, this, &fStringToFind, &fReplaceString, fCaseSensitive, fWrapAround, fBackSearch); fReplaceWindow->Show(); } else if (fReplaceWindow->IsHidden()) fReplaceWindow->Show(); else fReplaceWindow->Activate(); break; } case MSG_REPLACE: { message->FindBool("casesens", &fCaseSensitive); message->FindBool("wrap", &fWrapAround); message->FindBool("backsearch", &fBackSearch); message->FindString("FindText", &fStringToFind); message->FindString("ReplaceText", &fReplaceString); fFindAgainItem->SetEnabled(true); fReplaceSameItem->SetEnabled(true); _Replace(fStringToFind, fReplaceString, fCaseSensitive, fWrapAround, fBackSearch); Activate(); break; } case MENU_REPLACE_SAME: _Replace(fStringToFind, fReplaceString, fCaseSensitive, fWrapAround, fBackSearch); break; case MSG_REPLACE_ALL: { message->FindBool("casesens", &fCaseSensitive); message->FindString("FindText", &fStringToFind); message->FindString("ReplaceText", &fReplaceString); bool allWindows; message->FindBool("allwindows", &allWindows); fFindAgainItem->SetEnabled(true); fReplaceSameItem->SetEnabled(true); if (allWindows) SearchAllWindows(fStringToFind, fReplaceString, fCaseSensitive); else _ReplaceAll(fStringToFind, fReplaceString, fCaseSensitive); Activate(); break; } case B_NODE_MONITOR: _HandleNodeMonitorEvent(message); break; // Font menu case FONT_SIZE: { float fontSize; if (message->FindFloat("size", &fontSize) == B_OK) _SetFontSize(fontSize); break; } case FONT_FAMILY: { const char* fontFamily = NULL; const char* fontStyle = NULL; void* ptr; if (message->FindPointer("source", &ptr) == B_OK) { BMenuItem* item = static_cast(ptr); fontFamily = item->Label(); } BFont font; font.SetFamilyAndStyle(fontFamily, fontStyle); fItalicItem->SetMarked((font.Face() & B_ITALIC_FACE) != 0); fBoldItem->SetMarked((font.Face() & B_BOLD_FACE) != 0); fUnderlineItem->SetMarked((font.Face() & B_UNDERSCORE_FACE) != 0); _SetFontStyle(fontFamily, fontStyle); break; } case FONT_STYLE: { const char* fontFamily = NULL; const char* fontStyle = NULL; void* ptr; if (message->FindPointer("source", &ptr) == B_OK) { BMenuItem* item = static_cast(ptr); fontStyle = item->Label(); BMenu* menu = item->Menu(); if (menu != NULL) { BMenuItem* super_item = menu->Superitem(); if (super_item != NULL) fontFamily = super_item->Label(); } } BFont font; font.SetFamilyAndStyle(fontFamily, fontStyle); fItalicItem->SetMarked((font.Face() & B_ITALIC_FACE) != 0); fBoldItem->SetMarked((font.Face() & B_BOLD_FACE) != 0); fUnderlineItem->SetMarked((font.Face() & B_UNDERSCORE_FACE) != 0); _SetFontStyle(fontFamily, fontStyle); break; } case kMsgSetFontUp: { uint32 sameProperties; BFont font; fTextView->GetFontAndColor(&font, &sameProperties); //GetFont seems to return a constant size for font.Size(), //thus not used here (maybe that is a bug) int32 cur_size = (int32)font.Size(); for (unsigned int a = 0; a < sizeof(fontSizes)/sizeof(fontSizes[0]); a++) { if (fontSizes[a] > cur_size) { _SetFontSize(fontSizes[a]); break; } } break; } case kMsgSetFontDown: { uint32 sameProperties; BFont font; fTextView->GetFontAndColor(&font, &sameProperties); int32 cur_size = (int32)font.Size(); for (unsigned int a = 1; a < sizeof(fontSizes)/sizeof(fontSizes[0]); a++) { if (fontSizes[a] >= cur_size) { _SetFontSize(fontSizes[a-1]); break; } } break; } case kMsgSetItalic: { uint32 sameProperties; BFont font; fTextView->GetFontAndColor(&font, &sameProperties); if (fItalicItem->IsMarked()) font.SetFace(B_REGULAR_FACE); fItalicItem->SetMarked(!fItalicItem->IsMarked()); font_family family; font_style style; font.GetFamilyAndStyle(&family, &style); _SetFontStyle(family, style); break; } case kMsgSetBold: { uint32 sameProperties; BFont font; fTextView->GetFontAndColor(&font, &sameProperties); if (fBoldItem->IsMarked()) font.SetFace(B_REGULAR_FACE); fBoldItem->SetMarked(!fBoldItem->IsMarked()); font_family family; font_style style; font.GetFamilyAndStyle(&family, &style); _SetFontStyle(family, style); break; } case kMsgSetUnderline: { uint32 sameProperties; BFont font; fTextView->GetFontAndColor(&font, &sameProperties); if (fUnderlineItem->IsMarked()) font.SetFace(B_REGULAR_FACE); fUnderlineItem->SetMarked(!fUnderlineItem->IsMarked()); font_family family; font_style style; font.GetFamilyAndStyle(&family, &style); _SetFontStyle(family, style); break; } case FONT_COLOR: { ssize_t colorLength; rgb_color* color; if (message->FindData("color", B_RGB_COLOR_TYPE, (const void**)&color, &colorLength) == B_OK && colorLength == sizeof(rgb_color)) { /* * TODO: Ideally, when selecting the default color, * you wouldn't naively apply it; it shouldn't lose its nature. * When reloaded with a different default color, it should * reflect that different choice. */ _SetFontColor(color); } break; } // Document menu case ALIGN_LEFT: fTextView->SetAlignment(B_ALIGN_LEFT); _UpdateCleanUndoRedoSaveRevert(); break; case ALIGN_CENTER: fTextView->SetAlignment(B_ALIGN_CENTER); _UpdateCleanUndoRedoSaveRevert(); break; case ALIGN_RIGHT: fTextView->SetAlignment(B_ALIGN_RIGHT); _UpdateCleanUndoRedoSaveRevert(); break; case WRAP_LINES: { // update wrap setting if (fTextView->DoesWordWrap()) { fTextView->SetWordWrap(false); fWrapItem->SetMarked(false); } else { fTextView->SetWordWrap(true); fWrapItem->SetMarked(true); } // update buttons _UpdateCleanUndoRedoSaveRevert(); break; } case SHOW_STATISTICS: _ShowStatistics(); break; case ENABLE_ITEMS: fCutItem->SetEnabled(true); fCopyItem->SetEnabled(true); break; case DISABLE_ITEMS: fCutItem->SetEnabled(false); fCopyItem->SetEnabled(false); break; case TEXT_CHANGED: if (fUndoFlag) { if (fUndoCleans) { // we cleaned! fClean = true; fUndoCleans = false; } else if (fClean) { // if we were clean // then a redo will make us clean again fRedoCleans = true; fClean = false; } // set mode fCanUndo = false; fCanRedo = true; fUndoItem->SetLabel(B_TRANSLATE("Redo typing")); fUndoItem->SetEnabled(true); fUndoFlag = false; } else { if (fRedoFlag && fRedoCleans) { // we cleaned! fClean = true; fRedoCleans = false; } else if (fClean) { // if we were clean // then an undo will make us clean again fUndoCleans = true; fClean = false; } else { // no more cleaning from undo now... fUndoCleans = false; } // set mode fCanUndo = true; fCanRedo = false; fUndoItem->SetLabel(B_TRANSLATE("Undo typing")); fUndoItem->SetEnabled(true); fRedoFlag = false; } if (fClean) { fSaveItem->SetEnabled(fSaveMessage == NULL); } else { fSaveItem->SetEnabled(true); } fReloadItem->SetEnabled(fSaveMessage != NULL); fEncodingItem->SetEnabled(fSaveMessage != NULL); break; case SAVE_AS_ENCODING: void* ptr; if (message->FindPointer("source", &ptr) == B_OK && fSavePanelEncodingMenu != NULL) { fTextView->SetEncoding( (uint32)fSavePanelEncodingMenu->IndexOf((BMenuItem*)ptr)); } break; case UPDATE_STATUS: { message->AddBool("modified", !fClean); bool readOnly = !fTextView->IsEditable(); message->AddBool("readOnly", readOnly); if (readOnly) { BVolume volume(fNodeRef.device); message->AddBool("canUnlock", !volume.IsReadOnly()); } fStatusView->SetStatus(message); break; } case UPDATE_STATUS_REF: { entry_ref ref; const char* name; if (fSaveMessage != NULL && fSaveMessage->FindRef("directory", &ref) == B_OK && fSaveMessage->FindString("name", &name) == B_OK) { BDirectory dir(&ref); status_t status = dir.InitCheck(); BEntry entry; if (status == B_OK) status = entry.SetTo(&dir, name); if (status == B_OK) status = entry.GetRef(&ref); } fStatusView->SetRef(ref); break; } case UNLOCK_FILE: { status_t status = _UnlockFile(); if (status != B_OK) { BString text; bs_printf(&text, B_TRANSLATE("Unable to unlock file\n\t%s"), strerror(status)); _ShowAlert(text, B_TRANSLATE("OK"), "", "", B_STOP_ALERT); } PostMessage(UPDATE_STATUS); break; } case UPDATE_LINE_SELECTION: { int32 line; if (message->FindInt32("be:line", &line) == B_OK) { fTextView->GoToLine(line); fTextView->ScrollToSelection(); } int32 start, length; if (message->FindInt32("be:selection_offset", &start) == B_OK) { if (message->FindInt32("be:selection_length", &length) != B_OK) length = 0; fTextView->Select(start, start + length); fTextView->ScrollToOffset(start); } break; } default: BWindow::MessageReceived(message); break; } } void StyledEditWindow::MenusBeginning() { // update the font menu // unselect the old values if (fCurrentFontItem != NULL) { fCurrentFontItem->SetMarked(false); BMenu* menu = fCurrentFontItem->Submenu(); if (menu != NULL) { BMenuItem* item = menu->FindMarked(); if (item != NULL) item->SetMarked(false); } } if (fCurrentStyleItem != NULL) { fCurrentStyleItem->SetMarked(false); } BMenuItem* oldColorItem = fFontColorMenu->FindMarked(); if (oldColorItem != NULL) oldColorItem->SetMarked(false); BMenuItem* oldSizeItem = fFontSizeMenu->FindMarked(); if (oldSizeItem != NULL) oldSizeItem->SetMarked(false); // find the current font, color, size BFont font; uint32 sameProperties; rgb_color color = ui_color(B_DOCUMENT_TEXT_COLOR); bool sameColor; fTextView->GetFontAndColor(&font, &sameProperties, &color, &sameColor); color.alpha = 255; if (sameColor) { if (fDefaultFontColorItem->Color() == color) fDefaultFontColorItem->SetMarked(true); else { for (int i = 0; i < fFontColorMenu->CountItems(); i++) { ColorMenuItem* item = dynamic_cast (fFontColorMenu->ItemAt(i)); if (item != NULL && item->Color() == color) { item->SetMarked(true); break; } } } } if (sameProperties & B_FONT_SIZE) { if ((int)font.Size() == font.Size()) { // select the current font size char fontSizeStr[16]; snprintf(fontSizeStr, 15, "%i", (int)font.Size()); BMenuItem* item = fFontSizeMenu->FindItem(fontSizeStr); if (item != NULL) item->SetMarked(true); } } font_family family; font_style style; font.GetFamilyAndStyle(&family, &style); fCurrentFontItem = fFontMenu->FindItem(family); if (fCurrentFontItem != NULL) { fCurrentFontItem->SetMarked(true); BMenu* menu = fCurrentFontItem->Submenu(); if (menu != NULL) { BMenuItem* item = menu->FindItem(style); fCurrentStyleItem = item; if (fCurrentStyleItem != NULL) item->SetMarked(true); } } fBoldItem->SetMarked((font.Face() & B_BOLD_FACE) != 0); fItalicItem->SetMarked((font.Face() & B_ITALIC_FACE) != 0); fUnderlineItem->SetMarked((font.Face() & B_UNDERSCORE_FACE) != 0); switch (fTextView->Alignment()) { case B_ALIGN_LEFT: default: fAlignLeft->SetMarked(true); break; case B_ALIGN_CENTER: fAlignCenter->SetMarked(true); break; case B_ALIGN_RIGHT: fAlignRight->SetMarked(true); break; } // text encoding const BCharacterSet* charset = BCharacterSetRoster::GetCharacterSetByFontID(fTextView->GetEncoding()); BMenu* encodingMenu = fEncodingItem->Submenu(); if (charset != NULL && encodingMenu != NULL) { const char* mime = charset->GetMIMEName(); BString name(charset->GetPrintName()); if (mime) name << " (" << mime << ")"; BMenuItem* item = encodingMenu->FindItem(name); if (item != NULL) item->SetMarked(true); } } #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "SaveAlert" status_t StyledEditWindow::Save(BMessage* message) { _NodeMonitorSuspender nodeMonitorSuspender(this); if (!message) message = fSaveMessage; if (!message) return B_ERROR; entry_ref dirRef; const char* name; if (message->FindRef("directory", &dirRef) != B_OK || message->FindString("name", &name) != B_OK) return B_BAD_VALUE; BDirectory dir(&dirRef); BEntry entry(&dir, name); status_t status = B_ERROR; if (dir.InitCheck() == B_OK && entry.InitCheck() == B_OK) { struct stat st; BFile file(&entry, B_READ_WRITE | B_CREATE_FILE); if (file.InitCheck() == B_OK && (status = file.GetStat(&st)) == B_OK) { // check the file permissions if (!((getuid() == st.st_uid && (S_IWUSR & st.st_mode)) || (getgid() == st.st_gid && (S_IWGRP & st.st_mode)) || (S_IWOTH & st.st_mode))) { BString alertText; bs_printf(&alertText, B_TRANSLATE("This file is marked " "read-only. Save changes to the document \"%s\"? "), name); switch (_ShowAlert(alertText, B_TRANSLATE("Cancel"), B_TRANSLATE("Don't save"), B_TRANSLATE("Save"), B_WARNING_ALERT)) { case 0: return B_CANCELED; case 1: return B_OK; default: break; } } status = fTextView->WriteStyledEditFile(&file); } } if (status != B_OK) { BString alertText; bs_printf(&alertText, B_TRANSLATE("Error saving \"%s\":\n%s"), name, strerror(status)); _ShowAlert(alertText, B_TRANSLATE("OK"), "", "", B_STOP_ALERT); return status; } SetTitle(name); if (fSaveMessage != message) { delete fSaveMessage; fSaveMessage = new BMessage(*message); } // clear clean modes fSaveItem->SetEnabled(false); fUndoCleans = false; fRedoCleans = false; fClean = true; fNagOnNodeChange = true; PostMessage(UPDATE_STATUS); PostMessage(UPDATE_STATUS_REF); return status; } #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "Open_and_SaveAsPanel" status_t StyledEditWindow::SaveAs(BMessage* message) { if (fSavePanel == NULL) { entry_ref* directory = NULL; entry_ref dirRef; if (fSaveMessage != NULL) { if (fSaveMessage->FindRef("directory", &dirRef) == B_OK) directory = &dirRef; } BMessenger target(this); fSavePanel = new BFilePanel(B_SAVE_PANEL, &target, directory, B_FILE_NODE, false); BMenuBar* menuBar = dynamic_cast( fSavePanel->Window()->FindView("MenuBar")); if (menuBar != NULL) { fSavePanelEncodingMenu = new BMenu(B_TRANSLATE("Encoding")); fSavePanelEncodingMenu->SetRadioMode(true); menuBar->AddItem(fSavePanelEncodingMenu); BCharacterSetRoster roster; BCharacterSet charset; while (roster.GetNextCharacterSet(&charset) == B_NO_ERROR) { BString name(charset.GetPrintName()); const char* mime = charset.GetMIMEName(); if (mime) { name.Append(" ("); name.Append(mime); name.Append(")"); } BMenuItem * item = new BMenuItem(name.String(), new BMessage(SAVE_AS_ENCODING)); item->SetTarget(this); fSavePanelEncodingMenu->AddItem(item); if (charset.GetFontID() == fTextView->GetEncoding()) item->SetMarked(true); } } } fSavePanel->SetSaveText(Title()); if (message != NULL) fSavePanel->SetMessage(message); fSavePanel->Show(); return B_OK; } void StyledEditWindow::OpenFile(entry_ref* ref) { if (_LoadFile(ref) != B_OK) { fSaveItem->SetEnabled(true); // allow saving new files return; } fSaveMessage = new(std::nothrow) BMessage(B_SAVE_REQUESTED); if (fSaveMessage) { BEntry entry(ref, true); BEntry parent; entry_ref parentRef; char name[B_FILE_NAME_LENGTH]; entry.GetParent(&parent); entry.GetName(name); parent.GetRef(&parentRef); fSaveMessage->AddRef("directory", &parentRef); fSaveMessage->AddString("name", name); SetTitle(name); _LoadAttrs(); } _SwitchNodeMonitor(true, ref); PostMessage(UPDATE_STATUS_REF); fReloadItem->SetEnabled(fSaveMessage != NULL); fEncodingItem->SetEnabled(fSaveMessage != NULL); } status_t StyledEditWindow::PageSetup(const char* documentName) { BPrintJob printJob(documentName); if (fPrintSettings != NULL) printJob.SetSettings(new BMessage(*fPrintSettings)); status_t result = printJob.ConfigPage(); if (result == B_OK) { delete fPrintSettings; fPrintSettings = printJob.Settings(); } return result; } void StyledEditWindow::Print(const char* documentName) { BPrintJob printJob(documentName); if (fPrintSettings) printJob.SetSettings(new BMessage(*fPrintSettings)); if (printJob.ConfigJob() != B_OK) return; delete fPrintSettings; fPrintSettings = printJob.Settings(); // information from printJob BRect printableRect = printJob.PrintableRect(); int32 firstPage = printJob.FirstPage(); int32 lastPage = printJob.LastPage(); // lines eventually to be used to compute pages to print int32 firstLine = 0; int32 lastLine = fTextView->CountLines(); // values to be computed int32 pagesInDocument = 1; int32 linesInDocument = fTextView->CountLines(); int32 currentLine = 0; while (currentLine < linesInDocument) { float currentHeight = 0; while (currentHeight < printableRect.Height() && currentLine < linesInDocument) { currentHeight += fTextView->LineHeight(currentLine); if (currentHeight < printableRect.Height()) currentLine++; } if (pagesInDocument == lastPage) lastLine = currentLine - 1; if (currentHeight >= printableRect.Height()) { pagesInDocument++; if (pagesInDocument == firstPage) firstLine = currentLine; } } if (lastPage > pagesInDocument - 1) { lastPage = pagesInDocument - 1; lastLine = currentLine - 1; } printJob.BeginJob(); if (fTextView->CountLines() > 0 && fTextView->TextLength() > 0) { int32 printLine = firstLine; while (printLine <= lastLine) { float currentHeight = 0; int32 firstLineOnPage = printLine; while (currentHeight < printableRect.Height() && printLine <= lastLine) { currentHeight += fTextView->LineHeight(printLine); if (currentHeight < printableRect.Height()) printLine++; } float top = 0; if (firstLineOnPage != 0) top = fTextView->TextHeight(0, firstLineOnPage - 1); float bottom = fTextView->TextHeight(0, printLine - 1); BRect textRect(0.0, top + TEXT_INSET, printableRect.Width(), bottom + TEXT_INSET); printJob.DrawView(fTextView, textRect, B_ORIGIN); printJob.SpoolPage(); } } printJob.CommitJob(); } void StyledEditWindow::SearchAllWindows(BString find, BString replace, bool caseSensitive) { int32 numWindows; numWindows = be_app->CountWindows(); BMessage* message; message= new BMessage(MSG_REPLACE_ALL); message->AddString("FindText", find); message->AddString("ReplaceText", replace); message->AddBool("casesens", caseSensitive); while (numWindows >= 0) { StyledEditWindow* window = dynamic_cast( be_app->WindowAt(numWindows)); BMessenger messenger(window); messenger.SendMessage(message); numWindows--; } } bool StyledEditWindow::IsDocumentEntryRef(const entry_ref* ref) { if (ref == NULL) return false; if (fSaveMessage == NULL) return false; entry_ref dir; const char* name; if (fSaveMessage->FindRef("directory", &dir) != B_OK || fSaveMessage->FindString("name", &name) != B_OK) return false; entry_ref documentRef; BPath documentPath(&dir); documentPath.Append(name); get_ref_for_path(documentPath.Path(), &documentRef); return *ref == documentRef; } // #pragma mark - private methods #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "Menus" void StyledEditWindow::_InitWindow(uint32 encoding) { fPrintSettings = NULL; fSaveMessage = NULL; // undo modes fUndoFlag = false; fCanUndo = false; fRedoFlag = false; fCanRedo = false; // clean modes fUndoCleans = false; fRedoCleans = false; fClean = true; // search- state fReplaceString = ""; fStringToFind = ""; fCaseSensitive = false; fWrapAround = false; fBackSearch = false; fNagOnNodeChange = true; // add textview and scrollview BRect viewFrame = Bounds(); BRect textBounds = viewFrame; textBounds.OffsetTo(B_ORIGIN); fTextView = new StyledEditView(viewFrame, textBounds, this); fTextView->SetInsets(TEXT_INSET, TEXT_INSET, TEXT_INSET, TEXT_INSET); fTextView->SetDoesUndo(true); fTextView->SetStylable(true); fTextView->SetEncoding(encoding); fScrollView = new BScrollView("scrollview", fTextView, B_FOLLOW_ALL, 0, true, true, B_PLAIN_BORDER); fStatusView = new StatusView(fScrollView); fScrollView->AddChild(fStatusView); BMenuItem* openItem = new BMenuItem(BRecentFilesList::NewFileListMenu( B_TRANSLATE("Open" B_UTF8_ELLIPSIS), NULL, NULL, be_app, 9, true, NULL, APP_SIGNATURE), new BMessage(MENU_OPEN)); openItem->SetShortcut('O', 0); openItem->SetTarget(be_app); fSaveItem = new BMenuItem(B_TRANSLATE("Save"),new BMessage(MENU_SAVE), 'S'); fSaveItem->SetEnabled(false); fReloadItem = new BMenuItem(B_TRANSLATE("Reload" B_UTF8_ELLIPSIS), new BMessage(MENU_RELOAD), 'L'); fReloadItem->SetEnabled(false); fUndoItem = new BMenuItem(B_TRANSLATE("Can't undo"), new BMessage(B_UNDO), 'Z'); fUndoItem->SetEnabled(false); fCutItem = new BMenuItem(B_TRANSLATE("Cut"), new BMessage(B_CUT), 'X'); fCutItem->SetEnabled(false); fCopyItem = new BMenuItem(B_TRANSLATE("Copy"), new BMessage(B_COPY), 'C'); fCopyItem->SetEnabled(false); fFindAgainItem = new BMenuItem(B_TRANSLATE("Find again"), new BMessage(MENU_FIND_AGAIN), 'G'); fFindAgainItem->SetEnabled(false); fReplaceItem = new BMenuItem(B_TRANSLATE("Replace" B_UTF8_ELLIPSIS), new BMessage(MENU_REPLACE), 'R'); fReplaceSameItem = new BMenuItem(B_TRANSLATE("Replace next"), new BMessage(MENU_REPLACE_SAME), 'T'); fReplaceSameItem->SetEnabled(false); fFontSizeMenu = new BMenu(B_TRANSLATE("Size")); fFontSizeMenu->SetRadioMode(true); BMenuItem* menuItem; for (uint32 i = 0; i < sizeof(fontSizes) / sizeof(fontSizes[0]); i++) { BMessage* fontMessage = new BMessage(FONT_SIZE); fontMessage->AddFloat("size", fontSizes[i]); char label[64]; snprintf(label, sizeof(label), "%" B_PRId32, fontSizes[i]); fFontSizeMenu->AddItem(menuItem = new BMenuItem(label, fontMessage)); if (fontSizes[i] == (int32)be_plain_font->Size()) menuItem->SetMarked(true); } fFontColorMenu = new BMenu(B_TRANSLATE("Color"), 0, 0); fFontColorMenu->SetRadioMode(true); _BuildFontColorMenu(fFontColorMenu); fBoldItem = new BMenuItem(B_TRANSLATE("Bold"), new BMessage(kMsgSetBold)); fBoldItem->SetShortcut('B', 0); fItalicItem = new BMenuItem(B_TRANSLATE("Italic"), new BMessage(kMsgSetItalic)); fItalicItem->SetShortcut('I', 0); fUnderlineItem = new BMenuItem(B_TRANSLATE("Underline"), new BMessage(kMsgSetUnderline)); fUnderlineItem->SetShortcut('U', 0); fFontMenu = new BMenu(B_TRANSLATE("Font")); fCurrentFontItem = 0; fCurrentStyleItem = 0; // premake font menu since we cant add members dynamically later BLayoutBuilder::Menu<>(fFontMenu) .AddItem(fFontSizeMenu) .AddItem(fFontColorMenu) .AddSeparator() .AddItem(B_TRANSLATE("Increase size"), kMsgSetFontUp, '+') .AddItem(B_TRANSLATE("Decrease size"), kMsgSetFontDown, '-') .AddItem(fBoldItem) .AddItem(fItalicItem) .AddItem(fUnderlineItem) .AddSeparator() .End(); BMenu* subMenu; int32 numFamilies = count_font_families(); for (int32 i = 0; i < numFamilies; i++) { font_family family; if (get_font_family(i, &family) == B_OK) { subMenu = new BMenu(family); subMenu->SetRadioMode(true); fFontMenu->AddItem(new BMenuItem(subMenu, new BMessage(FONT_FAMILY))); int32 numStyles = count_font_styles(family); for (int32 j = 0; j < numStyles; j++) { font_style style; uint32 flags; if (get_font_style(family, j, &style, &flags) == B_OK) { subMenu->AddItem(new BMenuItem(style, new BMessage(FONT_STYLE))); } } } } // "Align"-subMenu: BMenu* alignMenu = new BMenu(B_TRANSLATE("Align")); alignMenu->SetRadioMode(true); alignMenu->AddItem(fAlignLeft = new BMenuItem(B_TRANSLATE("Left"), new BMessage(ALIGN_LEFT))); fAlignLeft->SetMarked(true); fAlignLeft->SetShortcut('L', B_OPTION_KEY); alignMenu->AddItem(fAlignCenter = new BMenuItem(B_TRANSLATE("Center"), new BMessage(ALIGN_CENTER))); fAlignCenter->SetShortcut('C', B_OPTION_KEY); alignMenu->AddItem(fAlignRight = new BMenuItem(B_TRANSLATE("Right"), new BMessage(ALIGN_RIGHT))); fAlignRight->SetShortcut('R', B_OPTION_KEY); fWrapItem = new BMenuItem(B_TRANSLATE("Wrap lines"), new BMessage(WRAP_LINES)); fWrapItem->SetMarked(true); fWrapItem->SetShortcut('W', B_OPTION_KEY); BMessage *message = new BMessage(MENU_RELOAD); message->AddString("encoding", "auto"); fEncodingItem = new BMenuItem(_PopulateEncodingMenu( new BMenu(B_TRANSLATE("Text encoding")), "UTF-8"), message); fEncodingItem->SetEnabled(false); BMenuBar* mainMenu = new BMenuBar("mainMenu"); BLayoutBuilder::Menu<>(mainMenu) .AddMenu(B_TRANSLATE("File")) .AddItem(B_TRANSLATE("New"), MENU_NEW, 'N') .AddItem(openItem) .AddSeparator() .AddItem(fSaveItem) .AddItem(B_TRANSLATE("Save as" B_UTF8_ELLIPSIS), MENU_SAVEAS, 'S', B_SHIFT_KEY) .AddItem(fReloadItem) .AddItem(B_TRANSLATE("Close"), MENU_CLOSE, 'W') .AddSeparator() .AddItem(B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS), MENU_PAGESETUP) .AddItem(B_TRANSLATE("Print" B_UTF8_ELLIPSIS), MENU_PRINT, 'P') .AddSeparator() .AddItem(B_TRANSLATE("Quit"), MENU_QUIT, 'Q') .End() .AddMenu(B_TRANSLATE("Edit")) .AddItem(fUndoItem) .AddSeparator() .AddItem(fCutItem) .AddItem(fCopyItem) .AddItem(B_TRANSLATE("Paste"), B_PASTE, 'V') .AddSeparator() .AddItem(B_TRANSLATE("Select all"), B_SELECT_ALL, 'A') .AddSeparator() .AddItem(B_TRANSLATE("Find" B_UTF8_ELLIPSIS), MENU_FIND, 'F') .AddItem(fFindAgainItem) .AddItem(B_TRANSLATE("Find selection"), MENU_FIND_SELECTION, 'H') .AddItem(fReplaceItem) .AddItem(fReplaceSameItem) .End() .AddItem(fFontMenu) .AddMenu(B_TRANSLATE("Document")) .AddItem(alignMenu) .AddItem(fWrapItem) .AddItem(fEncodingItem) .AddSeparator() .AddItem(B_TRANSLATE("Statistics" B_UTF8_ELLIPSIS), SHOW_STATISTICS) .End(); fSavePanel = NULL; fSavePanelEncodingMenu = NULL; BLayoutBuilder::Group<>(this, B_VERTICAL, 0) .Add(mainMenu) .AddGroup(B_VERTICAL, 0) .SetInsets(-1) .Add(fScrollView) .End() .End(); SetKeyMenuBar(mainMenu); // must focus text view after window layout is built fTextView->MakeFocus(true); } void StyledEditWindow::_BuildFontColorMenu(BMenu* menu) { if (menu == NULL) return; BFont font; menu->GetFont(&font); font_height fh; font.GetHeight(&fh); const float itemHeight = ceilf(fh.ascent + fh.descent + 2 * fh.leading); const float margin = 8.0; const int nbColumns = 5; BMessage msgTemplate(FONT_COLOR); BRect matrixArea(0, 0, 0, 0); // we place the color palette, reserving room at the top for (uint i = 0; i < sizeof(palette) / sizeof(rgb_color); i++) { BPoint topLeft((i % nbColumns) * (itemHeight + margin), (i / nbColumns) * (itemHeight + margin)); BRect buttonArea(topLeft.x, topLeft.y, topLeft.x + itemHeight, topLeft.y + itemHeight); buttonArea.OffsetBy(margin, itemHeight + margin + margin); menu->AddItem( new ColorMenuItem("", palette[i], new BMessage(msgTemplate)), buttonArea); buttonArea.OffsetBy(margin, margin); matrixArea = matrixArea | buttonArea; } // separator at the bottom to add spacing in the matrix menu matrixArea.top = matrixArea.bottom; menu->AddItem(new BSeparatorItem(), matrixArea); matrixArea.top = 0; matrixArea.bottom = itemHeight + 4; BMessage* msg = new BMessage(msgTemplate); msg->AddBool("default", true); fDefaultFontColorItem = new ColorMenuItem(B_TRANSLATE("Default"), ui_color(B_DOCUMENT_TEXT_COLOR), msg); menu->AddItem(fDefaultFontColorItem, matrixArea); matrixArea.top = matrixArea.bottom; matrixArea.bottom = matrixArea.top + margin; menu->AddItem(new BSeparatorItem(), matrixArea); } void StyledEditWindow::_LoadAttrs() { entry_ref dir; const char* name; if (fSaveMessage->FindRef("directory", &dir) != B_OK || fSaveMessage->FindString("name", &name) != B_OK) return; BPath documentPath(&dir); documentPath.Append(name); BNode documentNode(documentPath.Path()); if (documentNode.InitCheck() != B_OK) return; // info about position of caret may live in the file attributes int32 position = 0; if (documentNode.ReadAttr("be:caret_position", B_INT32_TYPE, 0, &position, sizeof(position)) != sizeof(position)) position = 0; fTextView->Select(position, position); fTextView->ScrollToOffset(position); BRect newFrame; ssize_t bytesRead = documentNode.ReadAttr(kInfoAttributeName, B_RECT_TYPE, 0, &newFrame, sizeof(BRect)); if (bytesRead != sizeof(BRect)) return; swap_data(B_RECT_TYPE, &newFrame, sizeof(BRect), B_SWAP_BENDIAN_TO_HOST); // Check if the frame in on screen, otherwise, ignore it BScreen screen(this); if (newFrame.Width() > 32 && newFrame.Height() > 32 && screen.Frame().Contains(newFrame)) { MoveTo(newFrame.left, newFrame.top); ResizeTo(newFrame.Width(), newFrame.Height()); } } void StyledEditWindow::_SaveAttrs() { if (!fSaveMessage) return; entry_ref dir; const char* name; if (fSaveMessage->FindRef("directory", &dir) != B_OK || fSaveMessage->FindString("name", &name) != B_OK) return; BPath documentPath(&dir); documentPath.Append(name); BNode documentNode(documentPath.Path()); if (documentNode.InitCheck() != B_OK) return; BRect frame(Frame()); swap_data(B_RECT_TYPE, &frame, sizeof(BRect), B_SWAP_HOST_TO_BENDIAN); documentNode.WriteAttr(kInfoAttributeName, B_RECT_TYPE, 0, &frame, sizeof(BRect)); // preserve caret line and position int32 start, end; fTextView->GetSelection(&start, &end); documentNode.WriteAttr("be:caret_position", B_INT32_TYPE, 0, &start, sizeof(start)); } #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "LoadAlert" status_t StyledEditWindow::_LoadFile(entry_ref* ref, const char* forceEncoding) { BEntry entry(ref, true); // traverse an eventual link status_t status = entry.InitCheck(); if (status == B_OK && entry.IsDirectory()) status = B_IS_A_DIRECTORY; BFile file; if (status == B_OK) status = file.SetTo(&entry, B_READ_ONLY); if (status == B_OK) status = fTextView->GetStyledText(&file, forceEncoding); if (status == B_ENTRY_NOT_FOUND) { // Treat non-existing files consideratley; we just want to get an // empty window for them - to create this new document status = B_OK; } if (status != B_OK) { // If an error occured, bail out and tell the user what happened BEntry entry(ref, true); char name[B_FILE_NAME_LENGTH]; if (entry.GetName(name) != B_OK) strlcpy(name, B_TRANSLATE("???"), sizeof(name)); BString text; if (status == B_BAD_TYPE) bs_printf(&text, B_TRANSLATE("Error loading \"%s\":\n\tUnsupported format"), name); else bs_printf(&text, B_TRANSLATE("Error loading \"%s\":\n\t%s"), name, strerror(status)); _ShowAlert(text, B_TRANSLATE("OK"), "", "", B_STOP_ALERT); return status; } struct stat st; if (file.InitCheck() == B_OK && file.GetStat(&st) == B_OK) { bool editable = (getuid() == st.st_uid && S_IWUSR & st.st_mode) || (getgid() == st.st_gid && S_IWGRP & st.st_mode) || (S_IWOTH & st.st_mode); BVolume volume(ref->device); editable = editable && !volume.IsReadOnly(); _SetReadOnly(!editable); } // update alignment switch (fTextView->Alignment()) { case B_ALIGN_LEFT: default: fAlignLeft->SetMarked(true); break; case B_ALIGN_CENTER: fAlignCenter->SetMarked(true); break; case B_ALIGN_RIGHT: fAlignRight->SetMarked(true); break; } // update word wrapping fWrapItem->SetMarked(fTextView->DoesWordWrap()); return B_OK; } #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "RevertToSavedAlert" void StyledEditWindow::_ReloadDocument(BMessage* message) { entry_ref ref; const char* name; if (fSaveMessage == NULL || message == NULL || fSaveMessage->FindRef("directory", &ref) != B_OK || fSaveMessage->FindString("name", &name) != B_OK) return; BDirectory dir(&ref); status_t status = dir.InitCheck(); BEntry entry; if (status == B_OK) status = entry.SetTo(&dir, name); if (status == B_OK) status = entry.GetRef(&ref); if (status != B_OK || !entry.Exists()) { BString alertText; bs_printf(&alertText, B_TRANSLATE("Cannot revert, file not found: \"%s\"."), name); _ShowAlert(alertText, B_TRANSLATE("OK"), "", "", B_STOP_ALERT); return; } if (!fClean) { BString alertText; bs_printf(&alertText, B_TRANSLATE("\"%s\" has unsaved changes.\n" "Revert it to the last saved version? "), Title()); if (_ShowAlert(alertText, B_TRANSLATE("Cancel"), B_TRANSLATE("Revert"), "", B_WARNING_ALERT) != 1) return; } const BCharacterSet* charset = BCharacterSetRoster::GetCharacterSetByFontID( fTextView->GetEncoding()); const char* forceEncoding = NULL; if (message->FindString("encoding", &forceEncoding) != B_OK) { if (charset != NULL) forceEncoding = charset->GetName(); } else { if (charset != NULL) { // UTF8 id assumed equal to -1 const uint32 idUTF8 = (uint32)-1; uint32 id = charset->GetConversionID(); if (strcmp(forceEncoding, "next") == 0) id = id == B_MS_WINDOWS_1250_CONVERSION ? idUTF8 : id + 1; else if (strcmp(forceEncoding, "previous") == 0) id = id == idUTF8 ? B_MS_WINDOWS_1250_CONVERSION : id - 1; const BCharacterSet* newCharset = BCharacterSetRoster::GetCharacterSetByConversionID(id); if (newCharset != NULL) forceEncoding = newCharset->GetName(); } } BScrollBar* vertBar = fScrollView->ScrollBar(B_VERTICAL); float vertPos = vertBar != NULL ? vertBar->Value() : 0.f; DisableUpdates(); fTextView->Reset(); status = _LoadFile(&ref, forceEncoding); if (vertBar != NULL) vertBar->SetValue(vertPos); EnableUpdates(); if (status != B_OK) return; #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "Menus" // clear undo modes fUndoItem->SetLabel(B_TRANSLATE("Can't undo")); fUndoItem->SetEnabled(false); fUndoFlag = false; fCanUndo = false; fRedoFlag = false; fCanRedo = false; // clear clean modes fSaveItem->SetEnabled(false); fUndoCleans = false; fRedoCleans = false; fClean = true; fNagOnNodeChange = true; } status_t StyledEditWindow::_UnlockFile() { _NodeMonitorSuspender nodeMonitorSuspender(this); if (!fSaveMessage) return B_ERROR; entry_ref dirRef; const char* name; if (fSaveMessage->FindRef("directory", &dirRef) != B_OK || fSaveMessage->FindString("name", &name) != B_OK) return B_BAD_VALUE; BDirectory dir(&dirRef); BEntry entry(&dir, name); status_t status = dir.InitCheck(); if (status != B_OK) return status; status = entry.InitCheck(); if (status != B_OK) return status; struct stat st; BFile file(&entry, B_READ_WRITE); status = file.InitCheck(); if (status != B_OK) return status; status = file.GetStat(&st); if (status != B_OK) return status; st.st_mode |= S_IWUSR; status = file.SetPermissions(st.st_mode); if (status == B_OK) _SetReadOnly(false); return status; } bool StyledEditWindow::_Search(BString string, bool caseSensitive, bool wrap, bool backSearch, bool scrollToOccurence) { int32 start; int32 finish; start = B_ERROR; int32 length = string.Length(); if (length == 0) return false; BString viewText(fTextView->Text()); int32 textStart, textFinish; fTextView->GetSelection(&textStart, &textFinish); if (backSearch) { if (caseSensitive) start = viewText.FindLast(string, textStart); else start = viewText.IFindLast(string, textStart); } else { if (caseSensitive) start = viewText.FindFirst(string, textFinish); else start = viewText.IFindFirst(string, textFinish); } if (start == B_ERROR && wrap) { if (backSearch) { if (caseSensitive) start = viewText.FindLast(string, viewText.Length()); else start = viewText.IFindLast(string, viewText.Length()); } else { if (caseSensitive) start = viewText.FindFirst(string, 0); else start = viewText.IFindFirst(string, 0); } } if (start != B_ERROR) { finish = start + length; fTextView->Select(start, finish); if (scrollToOccurence) fTextView->ScrollToSelection(); return true; } return false; } void StyledEditWindow::_FindSelection() { int32 selectionStart, selectionFinish; fTextView->GetSelection(&selectionStart, &selectionFinish); int32 selectionLength = selectionFinish- selectionStart; BString viewText = fTextView->Text(); viewText.CopyInto(fStringToFind, selectionStart, selectionLength); fFindAgainItem->SetEnabled(true); _Search(fStringToFind, fCaseSensitive, fWrapAround, fBackSearch); } bool StyledEditWindow::_Replace(BString findThis, BString replaceWith, bool caseSensitive, bool wrap, bool backSearch) { if (_Search(findThis, caseSensitive, wrap, backSearch)) { int32 start; int32 finish; fTextView->GetSelection(&start, &finish); _UpdateCleanUndoRedoSaveRevert(); fTextView->SetSuppressChanges(true); fTextView->Delete(start, start + findThis.Length()); fTextView->Insert(start, replaceWith.String(), replaceWith.Length()); fTextView->SetSuppressChanges(false); fTextView->Select(start, start + replaceWith.Length()); fTextView->ScrollToSelection(); return true; } return false; } void StyledEditWindow::_ReplaceAll(BString findThis, BString replaceWith, bool caseSensitive) { bool first = true; fTextView->SetSuppressChanges(true); // start from the beginning of text fTextView->Select(0, 0); // iterate occurences of findThis without wrapping around while (_Search(findThis, caseSensitive, false, false, false)) { if (first) { _UpdateCleanUndoRedoSaveRevert(); first = false; } int32 start; int32 finish; fTextView->GetSelection(&start, &finish); fTextView->Delete(start, start + findThis.Length()); fTextView->Insert(start, replaceWith.String(), replaceWith.Length()); // advance the caret behind the inserted text start += replaceWith.Length(); fTextView->Select(start, start); } fTextView->ScrollToSelection(); fTextView->SetSuppressChanges(false); } void StyledEditWindow::_SetFontSize(float fontSize) { uint32 sameProperties; BFont font; fTextView->GetFontAndColor(&font, &sameProperties); font.SetSize(fontSize); fTextView->SetFontAndColor(&font, B_FONT_SIZE); _UpdateCleanUndoRedoSaveRevert(); } void StyledEditWindow::_SetFontColor(const rgb_color* color) { uint32 sameProperties; BFont font; fTextView->GetFontAndColor(&font, &sameProperties, NULL, NULL); fTextView->SetFontAndColor(&font, 0, color); _UpdateCleanUndoRedoSaveRevert(); } void StyledEditWindow::_SetFontStyle(const char* fontFamily, const char* fontStyle) { BFont font; uint32 sameProperties; // find out what the old font was font_family oldFamily; font_style oldStyle; fTextView->GetFontAndColor(&font, &sameProperties); font.GetFamilyAndStyle(&oldFamily, &oldStyle); // clear that family's bit on the menu, if necessary if (strcmp(oldFamily, fontFamily)) { BMenuItem* oldItem = fFontMenu->FindItem(oldFamily); if (oldItem != NULL) { oldItem->SetMarked(false); BMenu* menu = oldItem->Submenu(); if (menu != NULL) { oldItem = menu->FindItem(oldStyle); if (oldItem != NULL) oldItem->SetMarked(false); } } } font.SetFamilyAndStyle(fontFamily, fontStyle); uint16 face = 0; if (!(font.Face() & B_REGULAR_FACE)) face = font.Face(); if (fBoldItem->IsMarked()) face |= B_BOLD_FACE; if (fItalicItem->IsMarked()) face |= B_ITALIC_FACE; if (fUnderlineItem->IsMarked()) face |= B_UNDERSCORE_FACE; font.SetFace(face); fTextView->SetFontAndColor(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_FACE); BMenuItem* superItem; superItem = fFontMenu->FindItem(fontFamily); if (superItem != NULL) { superItem->SetMarked(true); fCurrentFontItem = superItem; } _UpdateCleanUndoRedoSaveRevert(); } #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "Statistics" int32 StyledEditWindow::_ShowStatistics() { size_t words = 0; bool inWord = false; size_t length = fTextView->TextLength(); for (size_t i = 0; i < length; i++) { if (BUnicodeChar::IsWhitespace(fTextView->Text()[i])) { inWord = false; } else if (!inWord) { words++; inWord = true; } } BString result; result << B_TRANSLATE("Document statistics") << '\n' << '\n' << B_TRANSLATE("Lines:") << ' ' << fTextView->CountLines() << '\n' << B_TRANSLATE("Characters:") << ' ' << length << '\n' << B_TRANSLATE("Words:") << ' ' << words; BAlert* alert = new BAlert("Statistics", result, B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_EVEN_SPACING, B_INFO_ALERT); alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); return alert->Go(); } void StyledEditWindow::_SetReadOnly(bool readOnly) { fReplaceItem->SetEnabled(!readOnly); fReplaceSameItem->SetEnabled(!readOnly); fFontMenu->SetEnabled(!readOnly); fAlignLeft->Menu()->SetEnabled(!readOnly); fWrapItem->SetEnabled(!readOnly); fTextView->MakeEditable(!readOnly); } #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "Menus" void StyledEditWindow::_UpdateCleanUndoRedoSaveRevert() { fClean = false; fUndoCleans = false; fRedoCleans = false; fReloadItem->SetEnabled(fSaveMessage != NULL); fEncodingItem->SetEnabled(fSaveMessage != NULL); fSaveItem->SetEnabled(true); fUndoItem->SetLabel(B_TRANSLATE("Can't undo")); fUndoItem->SetEnabled(false); fCanUndo = false; fCanRedo = false; } int32 StyledEditWindow::_ShowAlert(const BString& text, const BString& label, const BString& label2, const BString& label3, alert_type type) const { const char* button2 = NULL; if (label2.Length() > 0) button2 = label2.String(); const char* button3 = NULL; button_spacing spacing = B_EVEN_SPACING; if (label3.Length() > 0) { button3 = label3.String(); spacing = B_OFFSET_SPACING; } BAlert* alert = new BAlert("Alert", text.String(), label.String(), button2, button3, B_WIDTH_AS_USUAL, spacing, type); alert->SetShortcut(0, B_ESCAPE); return alert->Go(); } BMenu* StyledEditWindow::_PopulateEncodingMenu(BMenu* menu, const char* currentEncoding) { menu->SetRadioMode(true); BString encoding(currentEncoding); if (encoding.Length() == 0) encoding.SetTo("UTF-8"); BCharacterSetRoster roster; BCharacterSet charset; while (roster.GetNextCharacterSet(&charset) == B_OK) { const char* mime = charset.GetMIMEName(); BString name(charset.GetPrintName()); if (mime) name << " (" << mime << ")"; BMessage *message = new BMessage(MENU_RELOAD); if (message != NULL) { message->AddString("encoding", charset.GetName()); BMenuItem* item = new BMenuItem(name, message); if (encoding.Compare(charset.GetName()) == 0) item->SetMarked(true); menu->AddItem(item); } } menu->AddSeparatorItem(); BMessage *message = new BMessage(MENU_RELOAD); message->AddString("encoding", "auto"); menu->AddItem(new BMenuItem(B_TRANSLATE("Autodetect"), message)); message = new BMessage(MENU_RELOAD); message->AddString("encoding", "next"); AddShortcut(B_PAGE_DOWN, B_OPTION_KEY, message); message = new BMessage(MENU_RELOAD); message->AddString("encoding", "previous"); AddShortcut(B_PAGE_UP, B_OPTION_KEY, message); return menu; } #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "NodeMonitorAlerts" void StyledEditWindow::_ShowNodeChangeAlert(const char* name, bool removed) { if (!fNagOnNodeChange) return; BString alertText(removed ? B_TRANSLATE("File \"%file%\" was removed by " "another application, recover it?") : B_TRANSLATE("File \"%file%\" was modified by " "another application, reload it?")); alertText.ReplaceAll("%file%", name); if (_ShowAlert(alertText, removed ? B_TRANSLATE("Recover") : B_TRANSLATE("Reload"), B_TRANSLATE("Ignore"), "", B_WARNING_ALERT) == 0) { if (!removed) { // supress the warning - user has already agreed fClean = true; BMessage msg(MENU_RELOAD); _ReloadDocument(&msg); } else Save(); } else fNagOnNodeChange = false; fSaveItem->SetEnabled(!fClean); } void StyledEditWindow::_HandleNodeMonitorEvent(BMessage *message) { int32 opcode = 0; if (message->FindInt32("opcode", &opcode) != B_OK) return; if (opcode != B_ENTRY_CREATED && message->FindInt64("node") != fNodeRef.node) // bypass foreign nodes' event return; switch (opcode) { case B_STAT_CHANGED: { int32 fields = 0; if (message->FindInt32("fields", &fields) == B_OK && (fields & (B_STAT_SIZE | B_STAT_MODIFICATION_TIME | B_STAT_MODE)) == 0) break; const char* name = NULL; if (fSaveMessage->FindString("name", &name) != B_OK) break; _ShowNodeChangeAlert(name, false); } break; case B_ENTRY_MOVED: { int32 device = 0; int64 srcFolder = 0; int64 dstFolder = 0; const char* name = NULL; if (message->FindInt32("device", &device) != B_OK || message->FindInt64("to directory", &dstFolder) != B_OK || message->FindInt64("from directory", &srcFolder) != B_OK || message->FindString("name", &name) != B_OK) break; entry_ref newRef(device, dstFolder, name); BEntry entry(&newRef); BEntry dirEntry; entry.GetParent(&dirEntry); entry_ref ref; dirEntry.GetRef(&ref); fSaveMessage->ReplaceRef("directory", &ref); fSaveMessage->ReplaceString("name", name); // store previous name - it may be useful in case // we have just moved to temporary copy of file (vim case) const char* sourceName = NULL; if (message->FindString("from name", &sourceName) == B_OK) { fSaveMessage->RemoveName("org.name"); fSaveMessage->AddString("org.name", sourceName); fSaveMessage->RemoveName("move time"); fSaveMessage->AddInt64("move time", system_time()); } SetTitle(name); if (srcFolder != dstFolder) { _SwitchNodeMonitor(false); _SwitchNodeMonitor(true); } PostMessage(UPDATE_STATUS_REF); } break; case B_ENTRY_REMOVED: { _SwitchNodeMonitor(false); fClean = false; // some editors like vim save files in following way: // 1) move t.txt -> t.txt~ // 2) re-create t.txt and write data to it // 3) remove t.txt~ // go to catch this case int32 device = 0; int64 directory = 0; BString orgName; if (fSaveMessage->FindString("org.name", &orgName) == B_OK && message->FindInt32("device", &device) == B_OK && message->FindInt64("directory", &directory) == B_OK) { // reuse the source name if it is not too old bigtime_t time = fSaveMessage->FindInt64("move time"); if ((system_time() - time) < 1000000) { entry_ref ref(device, directory, orgName); BEntry entry(&ref); if (entry.InitCheck() == B_OK) { _SwitchNodeMonitor(true, &ref); } fSaveMessage->ReplaceString("name", orgName); fSaveMessage->RemoveName("org.name"); fSaveMessage->RemoveName("move time"); SetTitle(orgName); _ShowNodeChangeAlert(orgName, false); break; } } const char* name = NULL; if (message->FindString("name", &name) != B_OK && fSaveMessage->FindString("name", &name) != B_OK) name = "Unknown"; _ShowNodeChangeAlert(name, true); PostMessage(UPDATE_STATUS_REF); } break; default: break; } } void StyledEditWindow::_SwitchNodeMonitor(bool on, entry_ref* ref) { if (!on) { watch_node(&fNodeRef, B_STOP_WATCHING, this); watch_node(&fFolderNodeRef, B_STOP_WATCHING, this); fNodeRef = node_ref(); fFolderNodeRef = node_ref(); return; } BEntry entry, folderEntry; if (ref != NULL) { entry.SetTo(ref, true); entry.GetParent(&folderEntry); } else if (fSaveMessage != NULL) { entry_ref ref; const char* name = NULL; if (fSaveMessage->FindRef("directory", &ref) != B_OK || fSaveMessage->FindString("name", &name) != B_OK) return; BDirectory dir(&ref); entry.SetTo(&dir, name); folderEntry.SetTo(&ref); } else return; if (entry.InitCheck() != B_OK || folderEntry.InitCheck() != B_OK) return; entry.GetNodeRef(&fNodeRef); folderEntry.GetNodeRef(&fFolderNodeRef); watch_node(&fNodeRef, B_WATCH_STAT, this); watch_node(&fFolderNodeRef, B_WATCH_DIRECTORY, this); }