#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <Alert.h>
#include <Beep.h>
#include <Clipboard.h>
#include <ControlLook.h>
#include <Debug.h>
#include <E-mail.h>
#include <Input.h>
#include <Locale.h>
#include <MenuItem.h>
#include <Mime.h>
#include <NodeInfo.h>
#include <NodeMonitor.h>
#include <Path.h>
#include <PopUpMenu.h>
#include <Region.h>
#include <Roster.h>
#include <ScrollView.h>
#include <TextView.h>
#include <UTF8.h>
#include <MailMessage.h>
#include <MailAttachment.h>
#include <mail_util.h>
#include "MailApp.h"
#include "MailSupport.h"
#include "MailWindow.h"
#include "Messages.h"
#include "Content.h"
#include "Utilities.h"
#include "FieldMsg.h"
#include "Words.h"
#define DEBUG_SPELLCHECK 0
#if DEBUG_SPELLCHECK
# define DSPELL(x) x
#else
# define DSPELL(x) ;
#endif
#define B_TRANSLATION_CONTEXT "Mail"
const rgb_color kSpellTextColor = {255, 0, 0, 255};
const rgb_color kHeaderColor = {72, 72, 72, 255};
const rgb_color kQuoteColors[] = {
{0, 0, 0xff, 0},
{0, 0xff, 0, 0},
{0xff, 0, 0, 0}
};
const int32 kNumQuoteColors = 3;
const rgb_color kDiffColors[] = {
{0xb0, 0, 0, 0},
{0, 0x90, 0, 0},
{0x6a, 0x6a, 0x6a, 0}
};
void Unicode2UTF8(int32 c, char **out);
inline bool
IsInitialUTF8Byte(uchar b)
{
return ((b & 0xC0) != 0x80);
}
void
Unicode2UTF8(int32 c, char **out)
{
char *s = *out;
ASSERT(c < 0x200000);
if (c < 0x80)
*(s++) = c;
else if (c < 0x800) {
*(s++) = 0xc0 | (c >> 6);
*(s++) = 0x80 | (c & 0x3f);
} else if (c < 0x10000) {
*(s++) = 0xe0 | (c >> 12);
*(s++) = 0x80 | ((c >> 6) & 0x3f);
*(s++) = 0x80 | (c & 0x3f);
} else if (c < 0x200000) {
*(s++) = 0xf0 | (c >> 18);
*(s++) = 0x80 | ((c >> 12) & 0x3f);
*(s++) = 0x80 | ((c >> 6) & 0x3f);
*(s++) = 0x80 | (c & 0x3f);
}
*out = s;
}
static bool
FilterHTMLTag(int32 &first, char **t, char *end)
{
const char *newlineTags[] = {
"br", "/p", "/div", "/table", "/tr",
NULL};
char *a = *t;
if (first == '&') {
if (a[1] == '#' && sscanf(a + 2, "%" B_SCNd32 ";", &first) == 1) {
t[0] += strchr(a, ';') - a;
return false;
}
const struct { const char *name; int32 code; } entities[] = {
{"AElig;", 0x00c6},
{"Aacute;", 0x00c1},
{"Acirc;", 0x00c2},
{"Agrave;", 0x00c0},
{"Aring;", 0x00c5},
{"Atilde;", 0x00c3},
{"Auml;", 0x00c4},
{"Ccedil;", 0x00c7},
{"Eacute;", 0x00c9},
{"Ecirc;", 0x00ca},
{"Egrave;", 0x00c8},
{"Euml;", 0x00cb},
{"Iacute;", 0x00cd},
{"Icirc;", 0x00ce},
{"Igrave;", 0x00cc},
{"Iuml;", 0x00cf},
{"Ntilde;", 0x00d1},
{"Oacute;", 0x00d3},
{"Ocirc;", 0x00d4},
{"Ograve;", 0x00d2},
{"Ouml;", 0x00d6},
{"Uacute;", 0x00da},
{"Ucirc;", 0x00db},
{"Ugrave;", 0x00d9},
{"Uuml;", 0x00dc},
{"aacute;", 0x00e1},
{"acirc;", 0x00e2},
{"aelig;", 0x00e6},
{"agrave;", 0x00e0},
{"amp;", '&'},
{"aring;", 0x00e5},
{"atilde;", 0x00e3},
{"auml;", 0x00e4},
{"ccedil;", 0x00e7},
{"copy;", 0x00a9},
{"eacute;", 0x00e9},
{"ecirc;", 0x00ea},
{"egrave;", 0x00e8},
{"euml;", 0x00eb},
{"gt;", '>'},
{"iacute;", 0x00ed},
{"icirc;", 0x00ee},
{"igrave;", 0x00ec},
{"iuml;", 0x00ef},
{"lt;", '<'},
{"nbsp;", ' '},
{"ntilde;", 0x00f1},
{"oacute;", 0x00f3},
{"ocirc;", 0x00f4},
{"ograve;", 0x00f2},
{"ouml;", 0x00f6},
{"quot;", '"'},
{"szlig;", 0x00df},
{"uacute;", 0x00fa},
{"ucirc;", 0x00fb},
{"ugrave;", 0x00f9},
{"uuml;", 0x00fc},
{NULL, 0}
};
for (int32 i = 0; entities[i].name; i++) {
int32 length = strlen(entities[i].name);
if (!strncmp(a + 1, entities[i].name, length)) {
t[0] += length;
first = entities[i].code;
return false;
}
}
}
if (first != '<')
return false;
a++;
bool newline = false;
for (int i = 0; newlineTags[i]; i++) {
int length = strlen(newlineTags[i]);
if (!strncasecmp(a, (char *)newlineTags[i], length) && !isalnum(a[length])) {
newline = true;
break;
}
}
if (!strncasecmp(a, "head", 4)) {
for (; a[0] && a < end; a++) {
if (strncasecmp (a, "</head", 6) == 0 ||
strncasecmp (a, "<body", 5) == 0)
break;
}
}
while (a[0] && a[0] != '>' && a < end)
a++;
t[0] = a;
if (newline) {
first = '\n';
return false;
}
return true;
}
static uint8
FindURL(const BString& string, int32 startIndex, int32& urlPos,
int32& urlLength, BString* urlString = NULL)
{
uint8 type = 0;
urlPos = string.Length();
int32 baseOffset = string.FindFirst("://", startIndex),
mailtoOffset = string.FindFirst("mailto:", startIndex);
if (baseOffset == B_ERROR && mailtoOffset == B_ERROR)
return 0;
if (baseOffset == B_ERROR)
baseOffset = string.Length();
if (mailtoOffset == B_ERROR)
mailtoOffset = string.Length();
if (baseOffset < mailtoOffset) {
type = TYPE_URL;
urlPos = baseOffset;
while (urlPos >= startIndex && (isalnum(string.ByteAt(urlPos - 1))
|| string.ByteAt(urlPos - 1) == '-'))
urlPos--;
} else if (mailtoOffset < baseOffset) {
type = TYPE_MAILTO;
urlPos = mailtoOffset;
}
const char* str = string.String() + urlPos;
urlLength = strcspn(str, " \t<>)\"\\,\r\n");
while (urlLength > 0) {
char suffix = str[urlLength - 1];
if (suffix != '.'
&& suffix != ','
&& suffix != '?'
&& suffix != '!'
&& suffix != ':'
&& suffix != ';')
break;
urlLength--;
}
if (urlString != NULL)
*urlString = BString(string.String() + urlPos, urlLength);
return type;
}
static void
CopyQuotes(const char *text, size_t length, char *outText, size_t &outLength)
{
const char *quote = QUOTE;
int32 level = 0;
for (size_t i = 0; i < length; i++) {
if (text[i] == quote[0])
level++;
else if (text[i] != ' ' && text[i] != '\t')
break;
}
if (level > 10)
level = kNumQuoteColors * 3 + (level % kNumQuoteColors);
const int32 quoteLength = strlen(QUOTE);
outLength = 0;
while (level-- > 0) {
strcpy(outText + outLength, QUOTE);
outLength += quoteLength;
}
}
int32
diff_mode(char c)
{
if (c == '+')
return 2;
if (c == '-')
return 1;
if (c == '@')
return 3;
if (c == ' ')
return 0;
return -1;
}
bool
is_quote_char(char c)
{
return c == '>' || c == '|';
}
void
FillInQuoteTextRuns(BTextView* view, quote_context* context, const char* line,
int32 length, const BFont& font, text_run_array* style, int32 maxStyles)
{
text_run* runs = style->runs;
int32 index = style->count;
bool begin;
int32 pos = 0;
int32 diffMode = 0;
bool inDiff = false;
bool wasDiff = false;
int32 level = 0;
if (context != NULL) {
level = context->level;
diffMode = context->diff_mode;
begin = context->begin;
inDiff = context->in_diff;
wasDiff = context->was_diff;
} else if (view != NULL) {
int32 start, end;
view->GetSelection(&end, &end);
begin = view->TextLength() == 0
|| view->ByteAt(view->TextLength() - 1) == '\n';
const char *text = view->Text();
if (!begin) {
for (start = end; start > 0; start--) {
if (text[start - 1] == '\n')
break;
}
}
if (!begin && start < end) {
begin = true;
diffMode = diff_mode(text[start]);
if (diffMode == 0) {
for (int32 i = start; i < end; i++) {
if (is_quote_char(text[i]))
level++;
else if (text[i] != ' ' && text[i] != '\t') {
begin = false;
break;
}
}
} else
inDiff = true;
if (begin) {
while (line[pos] == ' ')
pos++;
}
}
} else
begin = true;
for (int32 pos = 0; pos < length;) {
int32 next;
if (begin && is_quote_char(line[pos])) {
begin = false;
while (pos < length && line[pos] != '\n') {
level++;
bool search = true;
for (next = pos + 1; next < length; next++) {
if ((search && is_quote_char(line[next]))
|| line[next] == '\n')
break;
else if (search && line[next] != ' ' && line[next] != '\t')
search = false;
}
runs[index].offset = pos;
runs[index].font = font;
runs[index].color = level > 0 ? mix_color(ui_color(B_PANEL_TEXT_COLOR),
kQuoteColors[level % kNumQuoteColors], 120) : ui_color(B_PANEL_TEXT_COLOR);
pos = next;
if (++index >= maxStyles)
break;
}
} else {
if (begin) {
if (!inDiff) {
inDiff = !strncmp(&line[pos], "--- ", 4);
wasDiff = false;
}
if (inDiff) {
diffMode = diff_mode(line[pos]);
if (diffMode < 0) {
inDiff = false;
wasDiff = true;
}
}
}
runs[index].offset = pos;
runs[index].font = font;
if (wasDiff)
runs[index].color = kDiffColors[diff_mode('@') - 1];
else if (diffMode <= 0) {
runs[index].color = level > 0 ? mix_color(ui_color(B_PANEL_TEXT_COLOR),
kQuoteColors[level % kNumQuoteColors], 120) : ui_color(B_PANEL_TEXT_COLOR);
} else
runs[index].color = kDiffColors[diffMode - 1];
begin = false;
for (next = pos; next < length; next++) {
if (line[next] == '\n') {
begin = true;
wasDiff = false;
break;
}
}
pos = next;
index++;
}
if (pos < length)
begin = line[pos] == '\n';
if (begin) {
pos++;
level = 0;
wasDiff = false;
if (!inDiff && pos < length && line[pos] == ' ')
pos++;
}
if (index >= maxStyles)
break;
}
style->count = index;
if (context) {
context->level = level;
context->diff_mode = diffMode;
context->begin = begin;
context->in_diff = inDiff;
context->was_diff = wasDiff;
}
}
TextRunArray::TextRunArray(size_t entries)
:
fNumEntries(entries)
{
fArray = (text_run_array *)malloc(sizeof(int32) + sizeof(text_run) * entries);
if (fArray != NULL)
fArray->count = 0;
}
TextRunArray::~TextRunArray()
{
free(fArray);
}
TContentView::TContentView(bool incoming, BFont* font,
bool showHeader, bool coloredQuotes)
:
BView("m_content", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
fFocus(false),
fIncoming(incoming)
{
SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
BGroupLayout* layout = new BGroupLayout(B_VERTICAL, 0);
SetLayout(layout);
fTextView = new TTextView(fIncoming, this, font, showHeader,
coloredQuotes);
BScrollView* scrollView = new BScrollView("", fTextView, 0, true, true);
scrollView->SetBorders(BControlLook::B_TOP_BORDER);
AddChild(scrollView);
}
void
TContentView::FindString(const char *str)
{
int32 finish;
int32 pass = 0;
int32 start = 0;
if (str == NULL)
return;
const char *text = fTextView->Text();
int32 count = fTextView->TextLength();
fTextView->GetSelection(&start, &finish);
if (start != finish)
start = finish;
if (!count || text == NULL)
return;
while (pass < 2) {
long found = -1;
char lc = tolower(str[0]);
char uc = toupper(str[0]);
for (long i = start; i < count; i++) {
if (text[i] == lc || text[i] == uc) {
const char *s = str;
const char *t = text + i;
while (*s && (tolower(*s) == tolower(*t))) {
s++;
t++;
}
if (*s == 0) {
found = i;
break;
}
}
}
if (found != -1) {
Window()->Activate();
fTextView->Select(found, found + strlen(str));
fTextView->ScrollToSelection();
fTextView->MakeFocus(true);
return;
}
else if (start) {
start = 0;
text = fTextView->Text();
count = fTextView->TextLength();
pass++;
} else {
beep();
return;
}
}
}
void
TContentView::Focus(bool focus)
{
if (fFocus != focus) {
fFocus = focus;
Draw(Frame());
}
}
void
TContentView::MessageReceived(BMessage *msg)
{
switch (msg->what) {
case CHANGE_FONT:
{
BFont *font;
msg->FindPointer("font", (void **)&font);
fTextView->UpdateFont(font);
fTextView->Invalidate(Bounds());
break;
}
case M_ADD_QUOTE_LEVEL:
{
int32 start, finish;
fTextView->GetSelection(&start, &finish);
fTextView->AddQuote(start, finish);
break;
}
case M_SUB_QUOTE_LEVEL:
{
int32 start, finish;
fTextView->GetSelection(&start, &finish);
fTextView->RemoveQuote(start, finish);
break;
}
case M_SIGNATURE:
{
if (fTextView->IsReaderThreadRunning()) {
Window()->PostMessage(msg);
break;
}
entry_ref ref;
msg->FindRef("ref", &ref);
BFile file(&ref, B_READ_ONLY);
if (file.InitCheck() == B_OK) {
int32 start, finish;
fTextView->GetSelection(&start, &finish);
off_t size;
file.GetSize(&size);
if (size > 32768)
break;
char *signature = (char *)malloc(size);
if (signature == NULL)
break;
ssize_t bytesRead = file.Read(signature, size);
if (bytesRead < B_OK) {
free (signature);
break;
}
const char *text = fTextView->Text();
int32 length = fTextView->TextLength();
const char* newLines = "\n\n\n\n";
if (length && text[length - 1] == '\n')
newLines++;
fTextView->Select(length, length);
fTextView->Insert(newLines, strlen(newLines));
length += strlen(newLines);
fTextView->Select(length, length);
fTextView->Insert(signature, bytesRead);
fTextView->Select(length, length + bytesRead);
fTextView->ScrollToSelection();
fTextView->Select(length - 2 , length - 2);
fTextView->ScrollToSelection();
free (signature);
} else {
beep();
BAlert* alert = new BAlert("",
B_TRANSLATE("An error occurred trying to open this "
"signature."), B_TRANSLATE("Sorry"));
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
}
break;
}
case M_FIND:
FindString(msg->FindString("findthis"));
break;
default:
BView::MessageReceived(msg);
}
}
TTextView::TTextView(bool incoming, TContentView *view,
BFont *font, bool showHeader, bool coloredQuotes)
:
BTextView("", B_WILL_DRAW | B_NAVIGABLE),
fHeader(showHeader),
fColoredQuotes(coloredQuotes),
fReady(false),
fYankBuffer(NULL),
fLastPosition(-1),
fMail(NULL),
fFont(font),
fParent(view),
fStopLoading(false),
fThread(0),
fPanel(NULL),
fIncoming(incoming),
fSpellCheck(false),
fRaw(false),
fCursor(false),
fFirstSpellMark(NULL)
{
fStopSem = create_sem(1, "reader_sem");
SetStylable(true);
SetInsets(4, 4, 4, 4);
fEnclosures = new BList();
fEnclosureMenu = new BPopUpMenu("Enclosure", false, false);
fEnclosureMenu->SetFont(be_plain_font);
fEnclosureMenu->AddItem(new BMenuItem(
B_TRANSLATE("Save attachment" B_UTF8_ELLIPSIS), new BMessage(M_SAVE)));
fEnclosureMenu->AddItem(new BMenuItem(B_TRANSLATE("Open attachment"),
new BMessage(M_OPEN)));
fLinkMenu = new BPopUpMenu("Link", false, false);
fLinkMenu->SetFont(be_plain_font);
fLinkMenu->AddItem(new BMenuItem(B_TRANSLATE("Open this link"),
new BMessage(M_OPEN)));
fLinkMenu->AddItem(new BMenuItem(B_TRANSLATE("Copy link location"),
new BMessage(M_COPY)));
SetDoesUndo(true);
fUndoBuffer.On();
fInputMethodUndoBuffer.On();
fUndoState.replaced = false;
fUndoState.deleted = false;
fInputMethodUndoState.active = false;
fInputMethodUndoState.replace = false;
}
TTextView::~TTextView()
{
ClearList();
delete fPanel;
if (fYankBuffer)
free(fYankBuffer);
delete_sem(fStopSem);
}
void
TTextView::UpdateFont(const BFont* newFont)
{
fFont = *newFont;
text_run_array *runArray = RunArray(0, INT32_MAX);
for (int i = 0; i < runArray->count; i++)
runArray->runs[i].font = *newFont;
SetRunArray(0, INT32_MAX, runArray);
FreeRunArray(runArray);
}
void
TTextView::AttachedToWindow()
{
BTextView::AttachedToWindow();
fFont.SetSpacing(B_FIXED_SPACING);
SetFontAndColor(&fFont);
if (fMail != NULL) {
LoadMessage(fMail, false, NULL);
if (fIncoming)
MakeEditable(false);
}
}
void
TTextView::KeyDown(const char *key, int32 count)
{
char raw;
int32 end;
int32 start;
uint32 mods;
BMessage *msg;
int32 textLen = TextLength();
msg = Window()->CurrentMessage();
mods = msg->FindInt32("modifiers");
switch (key[0]) {
case B_HOME:
if (IsSelectable()) {
if (IsEditable())
BTextView::KeyDown(key, count);
else {
Select(0, 0);
ScrollToSelection();
}
}
break;
case B_END:
if (IsSelectable()) {
if (IsEditable())
BTextView::KeyDown(key, count);
else {
int32 length = TextLength();
Select(length, length);
ScrollToSelection();
}
}
break;
case 0x02:
if (IsSelectable()) {
GetSelection(&start, &end);
while (!IsInitialUTF8Byte(ByteAt(--start))) {
if (start < 0) {
start = 0;
break;
}
}
if (start >= 0) {
Select(start, start);
ScrollToSelection();
}
}
break;
case B_DELETE:
if (IsSelectable()) {
if ((key[0] == B_DELETE) || (mods & B_CONTROL_KEY)) {
if (IsEditable()) {
GetSelection(&start, &end);
if (start != end)
Delete();
else {
for (end = start + 1; !IsInitialUTF8Byte(ByteAt(end)); end++) {
if (end > textLen) {
end = textLen;
break;
}
}
Select(start, end);
Delete();
}
}
}
else
Select(textLen, textLen);
ScrollToSelection();
}
break;
case 0x05:
if (IsSelectable() && (mods & B_CONTROL_KEY)) {
if (CurrentLine() == CountLines() - 1)
Select(TextLength(), TextLength());
else {
GoToLine(CurrentLine() + 1);
GetSelection(&start, &end);
Select(start - 1, start - 1);
}
}
break;
case 0x06:
if (IsSelectable()) {
GetSelection(&start, &end);
if (end > start)
start = end;
else {
for (end = start + 1; !IsInitialUTF8Byte(ByteAt(end));
end++) {
if (end > textLen) {
end = textLen;
break;
}
}
start = end;
}
Select(start, start);
ScrollToSelection();
}
break;
case 0x0e:
if (IsSelectable()) {
raw = B_DOWN_ARROW;
BTextView::KeyDown(&raw, 1);
}
break;
case 0x0f:
if (IsEditable()) {
GetSelection(&start, &end);
Delete();
char newLine = '\n';
Insert(&newLine, 1);
Select(start, start);
ScrollToSelection();
}
break;
case B_PAGE_UP:
if (mods & B_CONTROL_KEY) {
if (IsEditable()) {
GetSelection(&start, &end);
if ((start != fLastPosition) && (fYankBuffer)) {
free(fYankBuffer);
fYankBuffer = NULL;
}
fLastPosition = start;
if (CurrentLine() < CountLines() - 1) {
GoToLine(CurrentLine() + 1);
GetSelection(&end, &end);
end--;
}
else
end = TextLength();
if (end < start)
break;
if (start == end)
end++;
Select(start, end);
if (fYankBuffer) {
char *result = (char *)realloc(fYankBuffer,
strlen(fYankBuffer) + (end - start) + 1);
if (result == NULL) {
free(fYankBuffer);
fYankBuffer = NULL;
break;
}
fYankBuffer = result;
GetText(start, end - start,
&fYankBuffer[strlen(fYankBuffer)]);
} else {
fYankBuffer = (char *)malloc(end - start + 1);
if (fYankBuffer == NULL)
break;
GetText(start, end - start, fYankBuffer);
}
Delete();
ScrollToSelection();
}
break;
}
BTextView::KeyDown(key, count);
break;
case 0x10:
if (IsSelectable()) {
raw = B_UP_ARROW;
BTextView::KeyDown(&raw, 1);
}
break;
case 0x19:
if (IsEditable() && fYankBuffer) {
Delete();
Insert(fYankBuffer);
ScrollToSelection();
}
break;
default:
BTextView::KeyDown(key, count);
}
}
void
TTextView::MakeFocus(bool focus)
{
if (!focus) {
fInputMethodUndoState.active = false;
if (fInputMethodUndoBuffer.CountItems() > 0) {
KUndoItem *item = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1);
if (item->History == K_INSERTED) {
fUndoBuffer.MakeNewUndoItem();
fUndoBuffer.AddUndo(item->RedoText, item->Length, item->Offset, item->History, item->CursorPos);
fUndoBuffer.MakeNewUndoItem();
}
fInputMethodUndoBuffer.MakeEmpty();
}
}
BTextView::MakeFocus(focus);
fParent->Focus(focus);
}
void
TTextView::MessageReceived(BMessage *msg)
{
switch (msg->what) {
case B_SIMPLE_DATA:
{
if (fIncoming)
break;
BMessage message(REFS_RECEIVED);
bool isEnclosure = false;
bool inserted = false;
off_t len = 0;
int32 end;
int32 start;
int32 index = 0;
entry_ref ref;
while (msg->FindRef("refs", index++, &ref) == B_OK) {
BFile file(&ref, B_READ_ONLY);
if (file.InitCheck() == B_OK) {
BNodeInfo node(&file);
char type[B_FILE_NAME_LENGTH];
node.GetType(type);
off_t size = 0;
file.GetSize(&size);
if (!strncasecmp(type, "text/", 5) && size > 0) {
len += size;
char *text = (char *)malloc(size);
if (text == NULL) {
puts("no memory!");
return;
}
if (file.Read(text, size) < B_OK) {
puts("could not read from file");
free(text);
continue;
}
if (!inserted) {
GetSelection(&start, &end);
Delete();
inserted = true;
}
int32 offset = 0;
for (int32 loop = 0; loop < size; loop++) {
if (text[loop] == '\n') {
Insert(&text[offset], loop - offset + 1);
offset = loop + 1;
} else if (text[loop] == '\r') {
text[loop] = '\n';
Insert(&text[offset], loop - offset + 1);
if ((loop + 1 < size)
&& (text[loop + 1] == '\n'))
loop++;
offset = loop + 1;
}
}
free(text);
} else {
isEnclosure = true;
message.AddRef("refs", &ref);
}
}
}
if (index == 1) {
BTextView::MessageReceived(msg);
break;
}
if (inserted)
Select(start, start + len);
if (isEnclosure)
Window()->PostMessage(&message, Window());
break;
}
case M_HEADER:
msg->FindBool("header", &fHeader);
SetText(NULL);
LoadMessage(fMail, false, NULL);
break;
case M_RAW:
StopLoad();
msg->FindBool("raw", &fRaw);
SetText(NULL);
LoadMessage(fMail, false, NULL);
break;
case M_SELECT:
if (IsSelectable())
Select(0, TextLength());
break;
case M_SAVE:
Save(msg);
break;
case B_NODE_MONITOR:
{
int32 opcode;
if (msg->FindInt32("opcode", &opcode) == B_NO_ERROR) {
dev_t device;
if (msg->FindInt32("device", &device) < B_OK)
break;
ino_t inode;
if (msg->FindInt64("node", &inode) < B_OK)
break;
hyper_text *enclosure;
for (int32 index = 0;
(enclosure = (hyper_text *)fEnclosures->ItemAt(index++)) != NULL;) {
if (device == enclosure->node.device
&& inode == enclosure->node.node) {
if (opcode == B_ENTRY_REMOVED) {
enclosure->saved = false;
enclosure->have_ref = false;
} else if (opcode == B_ENTRY_MOVED) {
enclosure->ref.device = device;
msg->FindInt64("to directory", &enclosure->ref.directory);
const char *name;
msg->FindString("name", &name);
enclosure->ref.set_name(name);
}
break;
}
}
}
break;
}
case B_COPY_TARGET:
{
BMessage data;
if (msg->FindMessage("be:originator-data", &data) == B_OK) {
entry_ref directory;
const char *name;
hyper_text *enclosure;
if (data.FindPointer("enclosure", (void **)&enclosure) == B_OK
&& msg->FindString("name", &name) == B_OK
&& msg->FindRef("directory", &directory) == B_OK) {
switch (enclosure->type) {
case TYPE_ENCLOSURE:
case TYPE_BE_ENCLOSURE:
{
BMessage saveMsg(M_SAVE);
saveMsg.AddString("name", name);
saveMsg.AddRef("directory", &directory);
saveMsg.AddPointer("enclosure", enclosure);
Save(&saveMsg, false);
break;
}
case TYPE_URL:
{
const char *replyType;
if (msg->FindString("be:filetypes", &replyType) != B_OK)
replyType = "application/x-vnd.Be-bookmark";
BDirectory dir(&directory);
BFile file(&dir, name, B_READ_WRITE);
if (file.InitCheck() == B_OK) {
if (strcmp(replyType, "application/x-vnd.Be-bookmark") == 0) {
file.WriteAttr("META:url", B_STRING_TYPE, 0,
enclosure->name, strlen(enclosure->name) + 1);
} else if (strcasecmp(replyType, "text/plain") == 0) {
file.Write(enclosure->name, strlen(enclosure->name));
}
BNodeInfo fileInfo(&file);
fileInfo.SetType(replyType);
}
break;
}
case TYPE_MAILTO:
{
char *addrStart = enclosure->name;
while (true) {
if (*addrStart == ':') {
addrStart++;
break;
}
if (*addrStart == '\0') {
addrStart = enclosure->name;
break;
}
addrStart++;
}
const char *replyType;
if (msg->FindString("be:filetypes", &replyType) != B_OK)
replyType = "application/x-vnd.Be-bookmark";
BDirectory dir(&directory);
BFile file(&dir, name, B_READ_WRITE);
if (file.InitCheck() == B_OK) {
if (!strcmp(replyType, "application/x-person")) {
file.WriteAttr("META:email", B_STRING_TYPE, 0,
addrStart, strlen(enclosure->name) + 1);
} else if (!strcasecmp(replyType, "text/plain")) {
file.Write(addrStart, strlen(addrStart));
}
BNodeInfo fileInfo(&file);
fileInfo.SetType(replyType);
}
break;
}
}
} else {
BTextView::MessageReceived(msg);
}
}
break;
}
case B_INPUT_METHOD_EVENT:
{
int32 im_op;
if (msg->FindInt32("be:opcode", &im_op) == B_OK) {
switch (im_op) {
case B_INPUT_METHOD_STARTED:
fInputMethodUndoState.replace = true;
fInputMethodUndoState.active = true;
break;
case B_INPUT_METHOD_STOPPED:
fInputMethodUndoState.active = false;
if (fInputMethodUndoBuffer.CountItems() > 0) {
KUndoItem *undo = fInputMethodUndoBuffer.ItemAt(
fInputMethodUndoBuffer.CountItems() - 1);
if (undo->History == K_INSERTED) {
fUndoBuffer.MakeNewUndoItem();
fUndoBuffer.AddUndo(undo->RedoText, undo->Length,
undo->Offset, undo->History, undo->CursorPos);
fUndoBuffer.MakeNewUndoItem();
}
fInputMethodUndoBuffer.MakeEmpty();
}
break;
case B_INPUT_METHOD_CHANGED:
fInputMethodUndoState.active = true;
break;
case B_INPUT_METHOD_LOCATION_REQUEST:
fInputMethodUndoState.active = true;
break;
}
}
BTextView::MessageReceived(msg);
break;
}
case M_REDO:
Redo();
break;
default:
BTextView::MessageReceived(msg);
}
}
void
TTextView::MouseDown(BPoint where)
{
if (IsEditable()) {
BPoint point;
uint32 buttons;
GetMouse(&point, &buttons);
if (gDictCount && (buttons == B_SECONDARY_MOUSE_BUTTON)) {
int32 offset, start, end, length;
const char *text = Text();
offset = OffsetAt(where);
if (isalpha(text[offset])) {
length = TextLength();
char c;
bool isAlpha, isApost, isCap;
int32 first;
for (first = offset;
(first >= 0) && (((c = text[first]) == '\'') || isalpha(c));
first--) {}
isCap = isupper(text[++first]);
for (start = offset, c = text[start], isAlpha = isalpha(c), isApost = (c=='\'');
(start >= 0) && (isAlpha || (isApost
&& (((c = text[start+1]) != 's') || !isCap) && isalpha(c)
&& isalpha(text[start-1])));
start--, c = text[start], isAlpha = isalpha(c), isApost = (c == '\'')) {}
start++;
for (end = offset, c = text[end], isAlpha = isalpha(c), isApost = (c == '\'');
(end < length) && (isAlpha || (isApost
&& (((c = text[end + 1]) != 's') || !isCap) && isalpha(c)));
end++, c = text[end], isAlpha = isalpha(c), isApost = (c == '\'')) {}
length = end - start;
BString srcWord;
srcWord.SetTo(text + start, length);
bool foundWord = false;
BList matches;
BString *string;
BMenuItem *menuItem;
BPopUpMenu menu("Words", false, false);
for (int32 i = 0; i < gDictCount; i++)
gWords[i]->FindBestMatches(&matches,
srcWord.String());
if (matches.CountItems()) {
sort_word_list(&matches, srcWord.String());
for (int32 i = 0; (string = (BString *)matches.ItemAt(i)) != NULL; i++) {
menu.AddItem((menuItem = new BMenuItem(string->String(), NULL)));
if (!strcasecmp(string->String(), srcWord.String())) {
menuItem->SetEnabled(false);
foundWord = true;
}
delete string;
}
} else {
menuItem = new BMenuItem(B_TRANSLATE("No matches"), NULL);
menuItem->SetEnabled(false);
menu.AddItem(menuItem);
}
BMenuItem *addItem = NULL;
if (!foundWord && gUserDict >= 0) {
menu.AddSeparatorItem();
addItem = new BMenuItem(B_TRANSLATE("Add"), NULL);
menu.AddItem(addItem);
}
point = ConvertToScreen(where);
if ((menuItem = menu.Go(point, false, false)) != NULL) {
if (menuItem == addItem) {
BString newItem(srcWord.String());
newItem << "\n";
gWords[gUserDict]->InitIndex();
gExactWords[gUserDict]->InitIndex();
gUserDictFile->Write(newItem.String(), newItem.Length());
gWords[gUserDict]->BuildIndex();
gExactWords[gUserDict]->BuildIndex();
if (fSpellCheck)
CheckSpelling(0, TextLength());
} else {
int32 len = strlen(menuItem->Label());
Select(start, start);
Delete(start, end);
Insert(start, menuItem->Label(), len);
Select(start+len, start+len);
}
}
}
return;
} else if (fSpellCheck && IsEditable()) {
int32 start, end;
GetSelection(&start, &end);
FindSpellBoundry(1, start, &start, &end);
CheckSpelling(start, end);
}
} else {
int32 clickOffset = OffsetAt(where);
int32 items = fEnclosures->CountItems();
for (int32 loop = 0; loop < items; loop++) {
hyper_text *enclosure = (hyper_text*) fEnclosures->ItemAt(loop);
if (clickOffset < enclosure->text_start || clickOffset >= enclosure->text_end)
continue;
int32 start;
int32 finish;
Select(enclosure->text_start, enclosure->text_end);
GetSelection(&start, &finish);
Window()->UpdateIfNeeded();
bool drag = false;
bool held = false;
uint32 buttons = 0;
if (Window()->CurrentMessage()) {
Window()->CurrentMessage()->FindInt32("buttons",
(int32 *) &buttons);
}
if (buttons != B_SECONDARY_MOUSE_BUTTON) {
BPoint point = where;
bigtime_t popupDelay;
get_click_speed(&popupDelay);
popupDelay *= 2;
popupDelay += system_time();
while (buttons && abs((int)(point.x - where.x)) < 4
&& abs((int)(point.y - where.y)) < 4
&& system_time() < popupDelay) {
snooze(10000);
GetMouse(&point, &buttons);
}
if (system_time() < popupDelay) {
if (!(abs((int)(point.x - where.x)) < 4
&& abs((int)(point.y - where.y)) < 4) && buttons)
drag = true;
} else {
held = true;
}
}
if (buttons == B_SECONDARY_MOUSE_BUTTON || held) {
BPoint point = where;
ConvertToScreen(&point);
BMenuItem *item;
if ((enclosure->type != TYPE_ENCLOSURE)
&& (enclosure->type != TYPE_BE_ENCLOSURE))
item = fLinkMenu->Go(point, true);
else
item = fEnclosureMenu->Go(point, true);
BMessage *msg;
if (item && (msg = item->Message()) != NULL) {
if (msg->what == M_SAVE) {
if (fPanel)
fPanel->SetEnclosure(enclosure);
else {
fPanel = new TSavePanel(enclosure, this);
fPanel->Window()->Show();
}
} else if (msg->what == M_COPY) {
if (be_clipboard->Lock()) {
be_clipboard->Clear();
BMessage *clip;
if ((clip = be_clipboard->Data()) != NULL) {
clip->AddData("text/plain", B_MIME_TYPE,
enclosure->name, strlen(enclosure->name));
be_clipboard->Commit();
}
be_clipboard->Unlock();
}
} else
Open(enclosure);
}
} else {
if (drag) {
BMessage dragMessage(B_SIMPLE_DATA);
dragMessage.AddInt32("be:actions", B_COPY_TARGET);
dragMessage.AddString("be:types", B_FILE_MIME_TYPE);
switch (enclosure->type) {
case TYPE_BE_ENCLOSURE:
case TYPE_ENCLOSURE:
dragMessage.AddString("be:types", B_FILE_MIME_TYPE);
dragMessage.AddString("be:filetypes",
enclosure->content_type ? enclosure->content_type : "");
dragMessage.AddString("be:clip_name", enclosure->name);
break;
case TYPE_URL:
dragMessage.AddString("be:types", B_FILE_MIME_TYPE);
dragMessage.AddString("be:filetypes",
"application/x-vnd.Be-bookmark");
dragMessage.AddString("be:filetypes", "text/plain");
dragMessage.AddString("be:clip_name", "Bookmark");
dragMessage.AddString("be:url", enclosure->name);
break;
case TYPE_MAILTO:
dragMessage.AddString("be:types", B_FILE_MIME_TYPE);
dragMessage.AddString("be:filetypes",
"application/x-person");
dragMessage.AddString("be:filetypes", "text/plain");
dragMessage.AddString("be:clip_name", "Person");
dragMessage.AddString("be:email", enclosure->name);
break;
default:
dragMessage.AddString("be:clip_name", "Hyperlink");
}
BMessage data;
data.AddPointer("enclosure", enclosure);
dragMessage.AddMessage("be:originator-data", &data);
BRegion selectRegion;
GetTextRegion(start, finish, &selectRegion);
DragMessage(&dragMessage, selectRegion.Frame(), this);
} else {
Open(enclosure);
}
}
return;
}
}
BTextView::MouseDown(where);
}
void
TTextView::MouseMoved(BPoint where, uint32 code, const BMessage *msg)
{
int32 start = OffsetAt(where);
for (int32 loop = fEnclosures->CountItems(); loop-- > 0;) {
hyper_text *enclosure = (hyper_text *)fEnclosures->ItemAt(loop);
if ((start >= enclosure->text_start) && (start < enclosure->text_end)) {
if (!fCursor)
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
fCursor = true;
return;
}
}
if (fCursor) {
SetViewCursor(B_CURSOR_I_BEAM);
fCursor = false;
}
BTextView::MouseMoved(where, code, msg);
}
void
TTextView::ClearList()
{
hyper_text *enclosure;
while ((enclosure = (hyper_text *)fEnclosures->FirstItem()) != NULL) {
fEnclosures->RemoveItem(enclosure);
if (enclosure->name)
free(enclosure->name);
if (enclosure->content_type)
free(enclosure->content_type);
if (enclosure->encoding)
free(enclosure->encoding);
if (enclosure->have_ref && !enclosure->saved) {
BEntry entry(&enclosure->ref);
entry.Remove();
}
watch_node(&enclosure->node, B_STOP_WATCHING, this);
free(enclosure);
}
}
void
TTextView::LoadMessage(BEmailMessage *mail, bool quoteIt, const char *text)
{
StopLoad();
fMail = mail;
ClearList();
MakeSelectable(true);
MakeEditable(false);
if (text)
Insert(text, strlen(text));
TTextView::Reader *reader = new TTextView::Reader(fHeader, fRaw, quoteIt, fIncoming,
text != NULL, true,
this, mail, fEnclosures, fStopSem);
resume_thread(fThread = spawn_thread(Reader::Run, "reader", B_NORMAL_PRIORITY, reader));
}
void
TTextView::Open(hyper_text *enclosure)
{
switch (enclosure->type) {
case TYPE_URL:
{
const struct {const char *urlType, *handler; } handlerTable[] = {
{"http", B_URL_HTTP},
{"https", B_URL_HTTPS},
{"ftp", B_URL_FTP},
{"gopher", B_URL_GOPHER},
{"mailto", B_URL_MAILTO},
{"news", B_URL_NEWS},
{"nntp", B_URL_NNTP},
{"telnet", B_URL_TELNET},
{"rlogin", B_URL_RLOGIN},
{"tn3270", B_URL_TN3270},
{"wais", B_URL_WAIS},
{"file", B_URL_FILE},
{NULL, NULL}
};
const char *handlerToLaunch = NULL;
const char *colonPos = strchr(enclosure->name, ':');
if (colonPos) {
int urlTypeLength = colonPos - enclosure->name;
for (int32 index = 0; handlerTable[index].urlType; index++) {
if (!strncasecmp(enclosure->name,
handlerTable[index].urlType, urlTypeLength)) {
handlerToLaunch = handlerTable[index].handler;
break;
}
}
}
if (handlerToLaunch) {
entry_ref appRef;
if (be_roster->FindApp(handlerToLaunch, &appRef) != B_OK)
handlerToLaunch = NULL;
}
if (!handlerToLaunch)
handlerToLaunch = "application/x-vnd.Be-Bookmark";
status_t result = be_roster->Launch(handlerToLaunch, 1, &enclosure->name);
if (result != B_NO_ERROR && result != B_ALREADY_RUNNING) {
beep();
BAlert* alert = new BAlert("",
B_TRANSLATE("There is no installed handler for "
"URL links."), B_TRANSLATE("Sorry"));
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
}
break;
}
case TYPE_MAILTO:
if (be_roster->Launch(B_MAIL_TYPE, 1, &enclosure->name) < B_OK) {
char *argv[] = {(char *)"Mail", enclosure->name};
be_app->ArgvReceived(2, argv);
}
break;
case TYPE_ENCLOSURE:
case TYPE_BE_ENCLOSURE:
if (!enclosure->have_ref) {
BPath path;
if (find_directory(B_SYSTEM_TEMP_DIRECTORY, &path) == B_NO_ERROR) {
BDirectory dir(path.Path());
if (dir.InitCheck() == B_NO_ERROR) {
char name[B_FILE_NAME_LENGTH];
char baseName[B_FILE_NAME_LENGTH];
strcpy(baseName, enclosure->name ? enclosure->name : "enclosure");
strcpy(name, baseName);
for (int32 index = 0; dir.Contains(name); index++) {
snprintf(name, B_FILE_NAME_LENGTH, "%s_%" B_PRId32,
baseName, index);
}
BEntry entry(path.Path());
entry_ref ref;
entry.GetRef(&ref);
BMessage save(M_SAVE);
save.AddRef("directory", &ref);
save.AddString("name", name);
save.AddPointer("enclosure", enclosure);
if (Save(&save) != B_NO_ERROR)
break;
enclosure->saved = false;
}
}
}
BMessenger tracker("application/x-vnd.Be-TRAK");
if (tracker.IsValid()) {
BMessage openMsg(B_REFS_RECEIVED);
openMsg.AddRef("refs", &enclosure->ref);
tracker.SendMessage(&openMsg);
}
break;
}
}
status_t
TTextView::Save(BMessage *msg, bool makeNewFile)
{
const char *name;
entry_ref ref;
BFile file;
BPath path;
hyper_text *enclosure;
status_t result = B_NO_ERROR;
char entry_name[B_FILE_NAME_LENGTH];
msg->FindString("name", &name);
msg->FindRef("directory", &ref);
msg->FindPointer("enclosure", (void **)&enclosure);
BDirectory dir;
dir.SetTo(&ref);
result = dir.InitCheck();
if (result == B_OK) {
if (makeNewFile) {
BEntry entry;
if (dir.FindEntry(name, &entry) == B_NO_ERROR)
entry.Remove();
if ((enclosure->have_ref) && (!enclosure->saved)) {
entry.SetTo(&enclosure->ref);
entry.GetName(entry_name);
result = entry.MoveTo(&dir, entry_name, true);
if (result == B_NO_ERROR) {
entry.Rename(name);
entry.GetRef(&enclosure->ref);
entry.GetNodeRef(&enclosure->node);
enclosure->saved = true;
return result;
}
}
if (result == B_NO_ERROR) {
result = dir.CreateFile(name, &file);
if (result == B_NO_ERROR && enclosure->content_type) {
char type[B_MIME_TYPE_LENGTH];
if (!strcasecmp(enclosure->content_type, "message/rfc822"))
strcpy(type, "text/x-email");
else if (!strcasecmp(enclosure->content_type, "message/delivery-status"))
strcpy(type, "text/plain");
else
strcpy(type, enclosure->content_type);
BNodeInfo info(&file);
info.SetType(type);
}
}
} else {
result = file.SetTo(&dir, name, B_WRITE_ONLY);
}
}
if (enclosure->component == NULL)
result = B_ERROR;
if (result == B_NO_ERROR) {
enclosure->component->GetDecodedData(&file);
BEntry entry;
dir.FindEntry(name, &entry);
entry.GetRef(&enclosure->ref);
enclosure->have_ref = true;
enclosure->saved = true;
entry.GetPath(&path);
update_mime_info(path.Path(), false, true,
!cistrcmp("application/octet-stream", enclosure->content_type ? enclosure->content_type : B_EMPTY_STRING));
entry.GetNodeRef(&enclosure->node);
watch_node(&enclosure->node, B_WATCH_NAME, this);
}
if (result != B_NO_ERROR) {
beep();
BAlert* alert = new BAlert("", B_TRANSLATE("An error occurred trying to save "
"the attachment."), B_TRANSLATE("Sorry"));
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
}
return result;
}
void
TTextView::StopLoad()
{
Window()->Unlock();
thread_info info;
if (fThread != 0 && get_thread_info(fThread, &info) == B_NO_ERROR) {
fStopLoading = true;
acquire_sem(fStopSem);
int32 result;
wait_for_thread(fThread, &result);
fThread = 0;
release_sem(fStopSem);
fStopLoading = false;
}
Window()->Lock();
}
bool
TTextView::IsReaderThreadRunning()
{
if (fThread == 0)
return false;
thread_info info;
for (int i = 5; i > 0; i--, usleep(100000))
if (get_thread_info(fThread, &info) != B_OK)
return false;
return true;
}
void
TTextView::AddAsContent(BEmailMessage *mail, bool wrap, uint32 charset, mail_encoding encoding)
{
if (mail == NULL)
return;
int32 textLength = TextLength();
const char *text = Text();
BTextMailComponent *body = mail->Body();
if (body == NULL) {
if (mail->SetBody(body = new BTextMailComponent()) < B_OK)
return;
}
body->SetEncoding(encoding, charset);
if (!wrap) {
body->AppendText(text);
return;
}
BWindow *window = Window();
char *saveText = strdup(text);
BRect saveTextRect = TextRect();
window->DisableUpdates();
Hide();
BScrollBar *vScroller = ScrollBar(B_VERTICAL);
BScrollBar *hScroller = ScrollBar(B_HORIZONTAL);
if (vScroller != NULL)
vScroller->SetTarget((BView *)NULL);
if (hScroller != NULL)
hScroller->SetTarget((BView *)NULL);
bool *boolArray;
int missingCharactersFixedWidth = 0;
int missingCharactersPreferredFont = 0;
int32 numberOfCharacters;
numberOfCharacters = BString(text).CountChars();
if (numberOfCharacters > 0
&& (boolArray = (bool *)malloc(sizeof(bool) * numberOfCharacters)) != NULL) {
memset(boolArray, 0, sizeof (bool) * numberOfCharacters);
be_fixed_font->GetHasGlyphs(text, numberOfCharacters, boolArray);
for (int i = 0; i < numberOfCharacters; i++) {
if (!boolArray[i])
missingCharactersFixedWidth += 1;
}
memset(boolArray, 0, sizeof (bool) * numberOfCharacters);
fFont.GetHasGlyphs(text, numberOfCharacters, boolArray);
for (int i = 0; i < numberOfCharacters; i++) {
if (!boolArray[i])
missingCharactersPreferredFont += 1;
}
free(boolArray);
}
if (missingCharactersFixedWidth > missingCharactersPreferredFont)
SetFontAndColor(0, textLength, &fFont);
else
SetFontAndColor(0, textLength, be_fixed_font);
BRect newTextRect = saveTextRect;
newTextRect.right = newTextRect.left + be_fixed_font->StringWidth("m") * 72;
SetTextRect(newTextRect);
int32 numLines = CountLines();
bool spaceMoved = false;
char *content = (char *)malloc(textLength + numLines * 72);
if (content != NULL) {
int32 contentLength = 0;
int32 nextUrlAt = 0, nextUrlLength = 0;
BString textStr(text);
FindURL(text, 0, nextUrlAt, nextUrlLength, NULL);
for (int32 i = 0; i < numLines; i++) {
int32 startOffset = OffsetAt(i);
if (spaceMoved) {
startOffset++;
spaceMoved = false;
}
int32 endOffset = OffsetAt(i + 1);
int32 lineLength = endOffset - startOffset;
if (nextUrlAt >= startOffset && nextUrlAt < endOffset
&& (nextUrlAt + nextUrlLength) > endOffset) {
int32 pos = nextUrlAt + nextUrlLength;
for (; text[pos]; pos++) {
if (isalnum(text[pos]) || isspace(text[pos]))
break;
}
if (text[pos] && isspace(text[pos]) && text[pos] != '\n')
pos++;
endOffset += pos - endOffset;
lineLength = endOffset - startOffset;
char buffer[64];
if (text[pos] == '\n')
buffer[0] = '\0';
else
strcpy(buffer, "\n");
size_t quoteLength;
CopyQuotes(text + startOffset, lineLength, buffer + strlen(buffer), quoteLength);
Insert(pos, buffer, strlen(buffer));
numLines = CountLines();
text = Text();
i++;
textStr = BString(text);
FindURL(text, endOffset, nextUrlAt, nextUrlLength, NULL);
}
if (text[endOffset - 1] != ' '
&& text[endOffset - 1] != '\n'
&& text[endOffset] == ' ') {
endOffset++;
lineLength++;
spaceMoved = true;
}
memcpy(content + contentLength, text + startOffset, lineLength);
contentLength += lineLength;
if ((text[endOffset - 1] != '\n') && (i < numLines - 1)) {
content[contentLength++] = '\n';
size_t quoteLength;
CopyQuotes(text + startOffset, lineLength, content + contentLength, quoteLength);
contentLength += quoteLength;
}
}
content[contentLength] = '\0';
body->AppendText(content);
free(content);
}
SetTextRect(saveTextRect);
SetText(saveText);
free(saveText);
SetFontAndColor(0, textLength, &fFont);
if (vScroller != NULL)
vScroller->SetTarget(this);
if (hScroller != NULL)
hScroller->SetTarget(this);
Show();
window->EnableUpdates();
}
TTextView::Reader::Reader(bool header, bool raw, bool quote, bool incoming,
bool stripHeader, bool mime, TTextView *view, BEmailMessage *mail,
BList *list, sem_id sem)
:
fHeader(header),
fRaw(raw),
fQuote(quote),
fIncoming(incoming),
fStripHeader(stripHeader),
fMime(mime),
fView(view),
fMail(mail),
fEnclosures(list),
fStopSem(sem)
{
}
bool
TTextView::Reader::ParseMail(BMailContainer *container,
BTextMailComponent *ignore)
{
int32 count = 0;
for (int32 i = 0; i < container->CountComponents(); i++) {
if (fView->fStopLoading)
return false;
BMailComponent *component;
if ((component = container->GetComponent(i)) == NULL) {
if (fView->fStopLoading)
return false;
hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text));
if (enclosure == NULL)
return false;
memset((void*)enclosure, 0, sizeof(hyper_text));
enclosure->type = TYPE_ENCLOSURE;
const char *name = "\n<Attachment: could not handle>\n";
fView->GetSelection(&enclosure->text_start, &enclosure->text_end);
enclosure->text_start++;
enclosure->text_end += strlen(name) - 1;
Insert(name, strlen(name), true);
fEnclosures->AddItem(enclosure);
continue;
}
count++;
if (component == ignore)
continue;
if (component->ComponentType() == B_MAIL_MULTIPART_CONTAINER) {
BMIMEMultipartMailContainer *c = dynamic_cast<BMIMEMultipartMailContainer *>(container->GetComponent(i));
ASSERT(c != NULL);
if (!ParseMail(c, ignore))
count--;
} else if (fIncoming) {
hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text));
if (enclosure == NULL)
return false;
memset((void*)enclosure, 0, sizeof(hyper_text));
enclosure->type = TYPE_ENCLOSURE;
enclosure->component = component;
BString name;
char fileName[B_FILE_NAME_LENGTH];
strcpy(fileName, "untitled");
if (BMailAttachment *attachment = dynamic_cast <BMailAttachment *> (component))
attachment->FileName(fileName);
BPath path(fileName);
enclosure->name = strdup(path.Leaf());
BMimeType type;
component->MIMEType(&type);
enclosure->content_type = strdup(type.Type());
char typeDescription[B_MIME_TYPE_LENGTH];
if (type.GetShortDescription(typeDescription) != B_OK)
strcpy(typeDescription, type.Type() ? type.Type() : B_EMPTY_STRING);
name = "\n<";
name.Append(B_TRANSLATE_COMMENT("Attachment: %name% (Type: %type%)",
"Don't translate the variables %name% and %type%."));
name.Append(">\n");
name.ReplaceFirst("%name%", enclosure->name);
name.ReplaceFirst("%type%", typeDescription);
fView->GetSelection(&enclosure->text_start, &enclosure->text_end);
enclosure->text_start++;
enclosure->text_end += strlen(name.String()) - 1;
Insert(name.String(), name.Length(), true);
fEnclosures->AddItem(enclosure);
}
}
return count > 0;
}
bool
TTextView::Reader::Process(const char *data, int32 data_len, bool isHeader)
{
char line[522];
int32 count = 0;
const BString dataStr(data, data_len);
BString nextUrl;
int32 nextUrlPos = 0, nextUrlLength = 0;
uint8 nextUrlType
= FindURL(dataStr, 0, nextUrlPos, nextUrlLength, &nextUrl);
for (int32 loop = 0; loop < data_len; loop++) {
if (fView->fStopLoading)
return false;
if (fQuote && (!loop || (loop && data[loop - 1] == '\n'))) {
strcpy(&line[count], QUOTE);
count += strlen(QUOTE);
}
if (!fRaw && fIncoming && (loop < data_len - 7)) {
uint8 type = (nextUrlPos == loop) ? nextUrlType : 0;
if (type) {
if (!Insert(line, count, false, isHeader))
return false;
count = 0;
hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text));
if (enclosure == NULL)
return false;
memset((void*)enclosure, 0, sizeof(hyper_text));
fView->GetSelection(&enclosure->text_start,
&enclosure->text_end);
enclosure->type = type;
enclosure->name = strdup(nextUrl.String());
if (enclosure->name == NULL) {
free(enclosure);
return false;
}
Insert(&data[loop], nextUrlLength, true, isHeader);
enclosure->text_end += nextUrlLength;
loop += nextUrlLength - 1;
fEnclosures->AddItem(enclosure);
nextUrlType
= FindURL(dataStr, loop,
nextUrlPos, nextUrlLength, &nextUrl);
continue;
}
}
if (!fRaw && fMime && data[loop] == '=') {
if ((loop) && (loop < data_len - 1) && (data[loop + 1] == '\r'))
loop += 2;
else
line[count++] = data[loop];
} else if (data[loop] != '\r')
line[count++] = data[loop];
if (count > 511 || (count && loop == data_len - 1)) {
if (!Insert(line, count, false, isHeader))
return false;
count = 0;
}
}
return true;
}
bool
TTextView::Reader::Insert(const char *line, int32 count, bool isHyperLink,
bool isHeader)
{
if (!count)
return true;
BFont font(fView->Font());
TextRunArray style(count / 8 + 8);
if (fView->fColoredQuotes && !isHeader && !isHyperLink) {
FillInQuoteTextRuns(fView, &fQuoteContext, line, count, font,
&style.Array(), style.MaxEntries());
} else {
text_run_array &array = style.Array();
array.count = 1;
array.runs[0].offset = 0;
if (isHeader) {
array.runs[0].color = isHyperLink ? ui_color(B_LINK_TEXT_COLOR) : kHeaderColor;
font.SetSize(font.Size() * 0.9);
} else {
array.runs[0].color = isHyperLink
? ui_color(B_LINK_TEXT_COLOR) : ui_color(B_PANEL_TEXT_COLOR);
}
array.runs[0].font = font;
}
if (!fView->Window()->Lock())
return false;
fView->Insert(fView->TextLength(), line, count, &style.Array());
fView->Window()->Unlock();
return true;
}
status_t
TTextView::Reader::Run(void *_this)
{
Reader *reader = (Reader *)_this;
TTextView *view = reader->fView;
char *msg = NULL;
off_t size = 0;
int32 len = 0;
if (!reader->Lock())
return B_INTERRUPTED;
BFile *file = dynamic_cast<BFile *>(reader->fMail->Data());
if (file != NULL) {
len = header_len(file);
if (reader->fHeader)
size = len;
if (reader->fRaw || !reader->fMime)
file->GetSize(&size);
if (size != 0 && (msg = (char *)malloc(size)) == NULL)
goto done;
file->Seek(0, 0);
if (msg)
size = file->Read(msg, size);
}
if (reader->fHeader && len) {
if (reader->fStripHeader) {
const char *header = msg;
char *buffer = NULL;
while (strncmp(header, "\r\n", 2)) {
const char *eol = header;
while ((eol = strstr(eol, "\r\n")) != NULL && isspace(eol[2]))
eol += 2;
if (eol == NULL)
break;
eol += 2;
size_t length = eol - header;
free(buffer);
buffer = (char *)malloc(length + 1);
if (buffer == NULL)
goto done;
memcpy(buffer, header, length);
length = rfc2047_to_utf8(&buffer, &length, length);
if (!strncasecmp(header, "Reply-To: ", 10)
|| !strncasecmp(header, "To: ", 4)
|| !strncasecmp(header, "From: ", 6)
|| !strncasecmp(header, "Subject: ", 8)
|| !strncasecmp(header, "Date: ", 6))
reader->Process(buffer, length, true);
header = eol;
}
free(buffer);
reader->Process("\r\n", 2, true);
}
else if (!reader->Process(msg, len, true))
goto done;
}
if (reader->fRaw) {
if (!reader->Process((const char *)msg + len, size - len))
goto done;
} else {
BEmailMessage *mail = reader->fMail;
BTextMailComponent *body = NULL;
if (mail->BodyText() && !view->fStopLoading) {
char *bodyText = const_cast<char *>(mail->BodyText());
int32 bodyLength = strlen(bodyText);
body = mail->Body();
bool isHTML = false;
BMimeType type;
if (body->MIMEType(&type) == B_OK && type == "text/html") {
char *t = bodyText, *a, *end = bodyText + bodyLength;
bodyText = (char *)malloc(bodyLength + 1);
isHTML = true;
for (a = bodyText; t < end; t++) {
int32 c = *t;
bool space = false;
while (c && (c == ' ' || c == '\t')) {
c = *(++t);
space = true;
}
if (space) {
c = ' ';
t--;
} else if (FilterHTMLTag(c, &t, end))
continue;
Unicode2UTF8(c, &a);
}
*a = 0;
bodyLength = strlen(bodyText);
body = NULL;
}
if (!reader->Process(bodyText, bodyLength))
goto done;
if (isHTML)
free(bodyText);
}
if (!reader->ParseMail(mail, body))
goto done;
}
if (!view->fStopLoading && view->Window()->Lock()) {
view->Select(0, 0);
view->MakeSelectable(true);
if (!reader->fIncoming)
view->MakeEditable(true);
view->Window()->Unlock();
}
done:
view->Window()->PostMessage(M_READ_POS);
reader->Unlock();
delete reader;
free(msg);
return B_NO_ERROR;
}
status_t
TTextView::Reader::Unlock()
{
return release_sem(fStopSem);
}
bool
TTextView::Reader::Lock()
{
if (acquire_sem_etc(fStopSem, 1, B_TIMEOUT, 0) != B_NO_ERROR)
return false;
return true;
}
TSavePanel::TSavePanel(hyper_text *enclosure, TTextView *view)
: BFilePanel(B_SAVE_PANEL)
{
fEnclosure = enclosure;
fView = view;
if (enclosure->name)
SetSaveText(enclosure->name);
}
void
TSavePanel::SendMessage(const BMessenger * , BMessage *msg)
{
const char *name = NULL;
BMessage save(M_SAVE);
entry_ref ref;
if ((!msg->FindRef("directory", &ref)) && (!msg->FindString("name", &name))) {
save.AddPointer("enclosure", fEnclosure);
save.AddString("name", name);
save.AddRef("directory", &ref);
fView->Window()->PostMessage(&save, fView);
}
}
void
TSavePanel::SetEnclosure(hyper_text *enclosure)
{
fEnclosure = enclosure;
if (enclosure->name)
SetSaveText(enclosure->name);
else
SetSaveText("");
if (!IsShowing())
Show();
Window()->Activate();
}
void
TTextView::InsertText(const char *insertText, int32 length, int32 offset,
const text_run_array *runs)
{
ContentChanged();
int32 cursorPos, dummy;
GetSelection(&cursorPos, &dummy);
if (fInputMethodUndoState.active) {
fInputMethodUndoBuffer.AddUndo(insertText, length, offset, K_INSERTED, cursorPos);
fInputMethodUndoState.replace = false;
} else {
if (fUndoState.replaced) {
fUndoBuffer.AddUndo(insertText, length, offset, K_REPLACED, cursorPos);
} else {
if (length == 1 && insertText[0] == 0x0a)
fUndoBuffer.MakeNewUndoItem();
fUndoBuffer.AddUndo(insertText, length, offset, K_INSERTED, cursorPos);
if (length == 1 && insertText[0] == 0x0a)
fUndoBuffer.MakeNewUndoItem();
}
}
fUndoState.replaced = false;
fUndoState.deleted = false;
struct text_runs : text_run_array { text_run _runs[1]; } style;
if (runs == NULL && IsEditable()) {
style.count = 1;
style.runs[0].offset = 0;
style.runs[0].font = fFont;
style.runs[0].color = ui_color(B_PANEL_TEXT_COLOR);
runs = &style;
}
BTextView::InsertText(insertText, length, offset, runs);
if (fSpellCheck && IsEditable())
{
UpdateSpellMarks(offset, length);
rgb_color color;
GetFontAndColor(offset - 1, NULL, &color);
const char *text = Text();
if (length > 1
|| isalpha(text[offset + 1])
|| (!isalpha(text[offset]) && text[offset] != '\'')
|| (color.red == kSpellTextColor.red
&& color.green == kSpellTextColor.green
&& color.blue == kSpellTextColor.blue))
{
int32 start, end;
FindSpellBoundry(length, offset, &start, &end);
DSPELL(printf("Offset %ld, start %ld, end %ld\n", offset, start, end));
DSPELL(printf("\t\"%10.10s...\"\n", text + start));
CheckSpelling(start, end);
}
}
}
void
TTextView::DeleteText(int32 start, int32 finish)
{
ContentChanged();
int32 cursorPos, dummy;
GetSelection(&cursorPos, &dummy);
if (fInputMethodUndoState.active) {
if (fInputMethodUndoState.replace) {
fUndoBuffer.AddUndo(&Text()[start], finish - start, start, K_DELETED, cursorPos);
fInputMethodUndoState.replace = false;
} else {
fInputMethodUndoBuffer.AddUndo(&Text()[start], finish - start, start,
K_DELETED, cursorPos);
}
} else
fUndoBuffer.AddUndo(&Text()[start], finish - start, start, K_DELETED, cursorPos);
fUndoState.deleted = true;
fUndoState.replaced = true;
BTextView::DeleteText(start, finish);
if (fSpellCheck && IsEditable()) {
UpdateSpellMarks(start, start - finish);
int32 s, e;
FindSpellBoundry(1, start, &s, &e);
CheckSpelling(s, e);
}
}
void
TTextView::ContentChanged(void)
{
BLooper *looper = Looper();
if (looper == NULL)
return;
BMessage msg(FIELD_CHANGED);
msg.AddInt32("bitmask", FIELD_BODY);
msg.AddPointer("source", this);
looper->PostMessage(&msg);
}
void
TTextView::CheckSpelling(int32 start, int32 end, int32 flags)
{
const char *text = Text();
const char *next, *endPtr, *word = NULL;
int32 wordLength = 0, wordOffset;
int32 nextHighlight = start;
BString testWord;
bool isCap = false;
bool isAlpha;
bool isApost;
rgb_color normalColor = ui_color(B_PANEL_TEXT_COLOR);
for (next = text + start, endPtr = text + end; next <= endPtr; next++) {
isAlpha = isalpha(*next);
isApost = (*next == '\'');
if (!word && isAlpha) {
word = next;
wordLength++;
isCap = isupper(*word);
} else if (word && (isAlpha || isApost) && !(isApost && !isalpha(next[1]))
&& !(isCap && isApost && (next[1] == 's'))) {
wordLength++;
} else if (word) {
if (wordLength > 1) {
bool isUpper = true;
for (int32 i = 0; i < wordLength; i++) {
if (word[i] == '\'')
break;
if (islower(word[i])) {
isUpper = false;
break;
}
}
if (!isUpper) {
bool foundMatch = false;
wordOffset = word - text;
testWord.SetTo(word, wordLength);
testWord = testWord.ToLower();
DSPELL(printf("Testing: \"%s\"\n", testWord.String()));
int32 key = -1;
if (gDictCount)
key = gExactWords[0]->GetKey(testWord.String());
for (int32 i = 0; i < gDictCount; i++) {
if (gExactWords[i]->Lookup(key) >= 0) {
foundMatch = true;
break;
}
}
if (!foundMatch) {
if (flags & S_CLEAR_ERRORS)
RemoveSpellMark(nextHighlight, wordOffset);
if (flags & S_SHOW_ERRORS)
AddSpellMark(wordOffset, wordOffset + wordLength);
} else if (flags & S_CLEAR_ERRORS)
RemoveSpellMark(nextHighlight, wordOffset + wordLength);
nextHighlight = wordOffset + wordLength;
}
}
word = NULL;
wordLength = 0;
}
}
if (nextHighlight <= end
&& (flags & S_CLEAR_ERRORS) != 0
&& nextHighlight < TextLength())
SetFontAndColor(nextHighlight, end, NULL, B_FONT_ALL, &normalColor);
}
void
TTextView::FindSpellBoundry(int32 length, int32 offset, int32 *_start, int32 *_end)
{
int32 start, end, textLength;
const char *text = Text();
textLength = TextLength();
for (start = offset - 1; start >= 0
&& (isalpha(text[start]) || text[start] == '\''); start--) {}
start++;
for (end = offset + length; end < textLength
&& (isalpha(text[end]) || text[end] == '\''); end++) {}
*_start = start;
*_end = end;
}
TTextView::spell_mark *
TTextView::FindSpellMark(int32 start, int32 end, spell_mark **_previousMark)
{
spell_mark *lastMark = NULL;
for (spell_mark *spellMark = fFirstSpellMark; spellMark; spellMark = spellMark->next) {
if (spellMark->start < end && spellMark->end > start) {
if (_previousMark)
*_previousMark = lastMark;
return spellMark;
}
lastMark = spellMark;
}
return NULL;
}
void
TTextView::UpdateSpellMarks(int32 offset, int32 length)
{
DSPELL(printf("UpdateSpellMarks: offset = %ld, length = %ld\n", offset, length));
spell_mark *spellMark;
for (spellMark = fFirstSpellMark; spellMark; spellMark = spellMark->next) {
DSPELL(printf("\tfound: %ld - %ld\n", spellMark->start, spellMark->end));
if (spellMark->end < offset)
continue;
if (spellMark->start > offset)
spellMark->start += length;
spellMark->end += length;
DSPELL(printf("\t-> reset: %ld - %ld\n", spellMark->start, spellMark->end));
}
}
status_t
TTextView::AddSpellMark(int32 start, int32 end)
{
DSPELL(printf("AddSpellMark: start = %ld, end = %ld\n", start, end));
spell_mark *spellMark = FindSpellMark(start, end);
if (spellMark) {
if (spellMark->start == start && spellMark->end == end) {
DSPELL(printf("\tfound one\n"));
return B_OK;
}
DSPELL(printf("\tremove old one\n"));
RemoveSpellMark(start, end);
}
spellMark = (spell_mark *)malloc(sizeof(spell_mark));
if (spellMark == NULL)
return B_NO_MEMORY;
spellMark->start = start;
spellMark->end = end;
spellMark->style = RunArray(start, end);
BFont font(fFont);
font.SetFace(B_BOLD_FACE | B_ITALIC_FACE);
SetFontAndColor(start, end, &font, B_FONT_ALL, &kSpellTextColor);
spellMark->next = fFirstSpellMark;
fFirstSpellMark = spellMark;
return B_OK;
}
bool
TTextView::RemoveSpellMark(int32 start, int32 end)
{
DSPELL(printf("RemoveSpellMark: start = %ld, end = %ld\n", start, end));
spell_mark *lastMark = NULL;
spell_mark *spellMark = FindSpellMark(start, end, &lastMark);
if (spellMark == NULL) {
DSPELL(printf("\tnot found!\n"));
return false;
}
DSPELL(printf("\tfound: %ld - %ld\n", spellMark->start, spellMark->end));
if (lastMark)
lastMark->next = spellMark->next;
else
fFirstSpellMark = spellMark->next;
if (spellMark->start < start)
start = spellMark->start;
if (spellMark->end > end)
end = spellMark->end;
SetRunArray(start, end, spellMark->style);
free(spellMark->style);
free(spellMark);
return true;
}
void
TTextView::RemoveSpellMarks()
{
spell_mark *spellMark, *nextMark;
for (spellMark = fFirstSpellMark; spellMark; spellMark = nextMark) {
nextMark = spellMark->next;
SetRunArray(spellMark->start, spellMark->end, spellMark->style);
free(spellMark->style);
free(spellMark);
}
fFirstSpellMark = NULL;
}
void
TTextView::EnableSpellCheck(bool enable)
{
if (fSpellCheck == enable)
return;
fSpellCheck = enable;
int32 textLength = TextLength();
if (fSpellCheck) {
int32 start, end;
GetSelection(&start, &end);
if (start != end)
Select(start, start);
CheckSpelling(0, textLength);
if (start != end)
Select(start, end);
}
else
RemoveSpellMarks();
}
void
TTextView::WindowActivated(bool flag)
{
if (!flag) {
fInputMethodUndoState.active = false;
if (fInputMethodUndoBuffer.CountItems() > 0) {
KUndoItem *item = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1);
if (item->History == K_INSERTED) {
fUndoBuffer.MakeNewUndoItem();
fUndoBuffer.AddUndo(item->RedoText, item->Length, item->Offset,
item->History, item->CursorPos);
fUndoBuffer.MakeNewUndoItem();
}
fInputMethodUndoBuffer.MakeEmpty();
}
}
BTextView::WindowActivated(flag);
}
void
TTextView::AddQuote(int32 start, int32 finish)
{
BRect rect = Bounds();
int32 lineStart;
GoToLine(CurrentLine());
GetSelection(&lineStart, &lineStart);
lineStart = LineStart(lineStart);
int32 lineEnd = finish > lineStart ? finish - 1 : finish;
{
const char *text = Text();
while (text[lineEnd] && text[lineEnd] != '\n')
lineEnd++;
}
Select(lineStart, lineEnd);
int32 textLength = lineEnd - lineStart;
char *text = (char *)malloc(textLength + 1);
if (text == NULL)
return;
GetText(lineStart, textLength, text);
int32 quoteLength = strlen(QUOTE);
int32 targetLength = 0;
char *target = NULL;
int32 lastLine = 0;
for (int32 index = 0; index < textLength; index++) {
if (text[index] == '\n' || index == textLength - 1) {
int32 lineLength = index - lastLine + 1;
char* result = (char *)realloc(target,
targetLength + lineLength + quoteLength);
if (result == NULL) {
free(target);
free(text);
return;
}
target = result;
memcpy(&target[targetLength], QUOTE, quoteLength);
targetLength += quoteLength;
memcpy(&target[targetLength], &text[lastLine], lineLength);
targetLength += lineLength;
lastLine = index + 1;
}
}
free(text);
Delete();
if (fColoredQuotes) {
const BFont *font = Font();
TextRunArray style(targetLength / 8 + 8);
FillInQuoteTextRuns(NULL, NULL, target, targetLength, font,
&style.Array(), style.MaxEntries());
Insert(target, targetLength, &style.Array());
} else
Insert(target, targetLength);
free(target);
Select(start + quoteLength, finish + (targetLength - textLength));
ScrollTo(rect.LeftTop());
}
void
TTextView::RemoveQuote(int32 start, int32 finish)
{
BRect rect = Bounds();
GoToLine(CurrentLine());
int32 lineStart;
GetSelection(&lineStart, &lineStart);
lineStart = LineStart(lineStart);
int32 lineEnd = finish > lineStart ? finish - 1 : finish;
const char *text = Text();
while (text[lineEnd] && text[lineEnd] != '\n')
lineEnd++;
Select(lineStart, lineEnd);
int32 length = lineEnd - lineStart;
char *target = (char *)malloc(length + 1);
if (target == NULL)
return;
int32 quoteLength = strlen(QUOTE);
int32 removed = 0;
text += lineStart;
for (int32 index = 0; index < length;) {
int32 lineLength = 0;
while (index + lineLength < length && text[lineLength] != '\n')
lineLength++;
if (text[lineLength] == '\n' && index + lineLength + 1 < length)
lineLength++;
if (!strncmp(text, QUOTE, quoteLength)) {
length -= quoteLength;
removed += quoteLength;
lineLength -= quoteLength;
text += quoteLength;
}
if (lineLength == 0) {
target[index] = '\0';
break;
}
memcpy(&target[index], text, lineLength);
text += lineLength;
index += lineLength;
}
if (removed) {
Delete();
if (fColoredQuotes) {
const BFont *font = Font();
TextRunArray style(length / 8 + 8);
FillInQuoteTextRuns(NULL, NULL, target, length, font,
&style.Array(), style.MaxEntries());
Insert(target, length, &style.Array());
} else
Insert(target, length);
bool noSelection = start == finish;
if (start > lineStart + quoteLength)
start -= quoteLength;
else
start = lineStart;
if (noSelection)
finish = start;
else
finish -= removed;
}
free(target);
Select(start, finish);
ScrollTo(rect.LeftTop());
}
int32
TTextView::LineStart(int32 offset)
{
if (offset <= 0)
return 0;
while (offset > 0) {
offset = PreviousByte(offset);
if (ByteAt(offset) == B_ENTER)
return offset + 1;
}
return offset;
}
int32
TTextView::PreviousByte(int32 offset) const
{
if (offset <= 0)
return 0;
int32 count = 6;
for (--offset; offset > 0 && count; --offset, --count) {
if ((ByteAt(offset) & 0xC0) != 0x80)
break;
}
return count ? offset : 0;
}
void
TTextView::Undo(BClipboard *)
{
if (fInputMethodUndoState.active)
return;
int32 length, offset, cursorPos;
undo_type history;
char *text;
status_t status;
status = fUndoBuffer.Undo(&text, &length, &offset, &history, &cursorPos);
if (status == B_OK) {
fUndoBuffer.Off();
switch (history) {
case K_INSERTED:
BTextView::Delete(offset, offset + length);
Select(offset, offset);
break;
case K_DELETED:
BTextView::Insert(offset, text, length);
Select(offset, offset + length);
break;
case K_REPLACED:
BTextView::Delete(offset, offset + length);
status = fUndoBuffer.Undo(&text, &length, &offset, &history, &cursorPos);
if (status == B_OK && history == K_DELETED) {
BTextView::Insert(offset, text, length);
Select(offset, offset + length);
} else {
::beep();
BAlert* alert = new BAlert("",
B_TRANSLATE("Inconsistency occurred in the undo/redo "
"buffer."), B_TRANSLATE("OK"));
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
}
break;
}
ScrollToSelection();
ContentChanged();
fUndoBuffer.On();
}
}
void
TTextView::Redo()
{
if (fInputMethodUndoState.active)
return;
int32 length, offset, cursorPos;
undo_type history;
char *text;
status_t status;
bool replaced;
status = fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced);
if (status == B_OK) {
fUndoBuffer.Off();
switch (history) {
case K_INSERTED:
BTextView::Insert(offset, text, length);
Select(offset, offset + length);
break;
case K_DELETED:
BTextView::Delete(offset, offset + length);
if (replaced) {
fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced);
BTextView::Insert(offset, text, length);
}
Select(offset, offset + length);
break;
case K_REPLACED:
::beep();
BAlert* alert = new BAlert("",
B_TRANSLATE("Inconsistency occurred in the undo/redo "
"buffer."), B_TRANSLATE("OK"));
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
break;
}
ScrollToSelection();
ContentChanged();
fUndoBuffer.On();
}
}