haiku/src/apps/stylededit/StyledEditWindow.cpp

2256 lines
54 KiB
C++

/*
* 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 <Alert.h>
#include <Autolock.h>
#include <Catalog.h>
#include <CharacterSet.h>
#include <CharacterSetRoster.h>
#include <Clipboard.h>
#include <Debug.h>
#include <File.h>
#include <FilePanel.h>
#include <fs_attr.h>
#include <LayoutBuilder.h>
#include <Locale.h>
#include <Menu.h>
#include <MenuBar.h>
#include <MenuItem.h>
#include <NodeMonitor.h>
#include <Path.h>
#include <PrintJob.h>
#include <RecentItems.h>
#include <Rect.h>
#include <Roster.h>
#include <Screen.h>
#include <ScrollView.h>
#include <TextControl.h>
#include <TextView.h>
#include <TranslationUtils.h>
#include <UnicodeChar.h>
#include <UTF8.h>
#include <Volume.h>
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<StyledEditApp*>(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;
break;
}
case MSG_REPLACE_WINDOW_QUIT:
{
fReplaceWindow = NULL;
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);
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);
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);
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<BMenuItem*>(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<BMenuItem*>(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<ColorMenuItem*>
(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<BMenuBar*>(
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<StyledEditWindow *>(
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);
fTextView->MakeFocus(true);
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);
}
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);
}