libroot/posix/stdio: Remove unused portions.
[haiku.git] / src / apps / mail / Content.cpp
blobe03647573f4aed988b5e4b83dc5aceebd1d774d5
1 /*
2 Open Tracker License
4 Terms and Conditions
6 Copyright (c) 1991-2001, Be Incorporated. All rights reserved.
8 Permission is hereby granted, free of charge, to any person obtaining a copy of
9 this software and associated documentation files (the "Software"), to deal in
10 the Software without restriction, including without limitation the rights to
11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 of the Software, and to permit persons to whom the Software is furnished to do
13 so, subject to the following conditions:
15 The above copyright notice and this permission notice applies to all licensees
16 and shall be included in all copies or substantial portions of the Software.
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN
23 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 Except as contained in this notice, the name of Be Incorporated shall not be
26 used in advertising or otherwise to promote the sale, use or other dealings in
27 this Software without prior written authorization from Be Incorporated.
29 BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or
30 registered trademarks of Be Incorporated in the United States and other
31 countries. Other brand product names are registered trademarks or trademarks
32 of their respective holders. All rights reserved.
36 #include <ctype.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <strings.h>
41 #include <Alert.h>
42 #include <Beep.h>
43 #include <Clipboard.h>
44 #include <ControlLook.h>
45 #include <Debug.h>
46 #include <E-mail.h>
47 #include <Input.h>
48 #include <Locale.h>
49 #include <MenuItem.h>
50 #include <Mime.h>
51 #include <NodeInfo.h>
52 #include <NodeMonitor.h>
53 #include <Path.h>
54 #include <PopUpMenu.h>
55 #include <Region.h>
56 #include <Roster.h>
57 #include <ScrollView.h>
58 #include <TextView.h>
59 #include <UTF8.h>
61 #include <MailMessage.h>
62 #include <MailAttachment.h>
63 #include <mail_util.h>
65 #include "MailApp.h"
66 #include "MailSupport.h"
67 #include "MailWindow.h"
68 #include "Messages.h"
69 #include "Content.h"
70 #include "Utilities.h"
71 #include "FieldMsg.h"
72 #include "Words.h"
75 #define DEBUG_SPELLCHECK 0
76 #if DEBUG_SPELLCHECK
77 # define DSPELL(x) x
78 #else
79 # define DSPELL(x) ;
80 #endif
83 #define B_TRANSLATION_CONTEXT "Mail"
86 const rgb_color kNormalTextColor = {0, 0, 0, 255};
87 const rgb_color kSpellTextColor = {255, 0, 0, 255};
88 const rgb_color kHyperLinkColor = {0, 0, 255, 255};
89 const rgb_color kHeaderColor = {72, 72, 72, 255};
91 const rgb_color kQuoteColors[] = {
92 {0, 0, 0x80, 0}, // 3rd, 6th, ... quote level color (blue)
93 {0, 0x80, 0, 0}, // 1st, 4th, ... quote level color (green)
94 {0x80, 0, 0, 0} // 2nd, ... (red)
96 const int32 kNumQuoteColors = 3;
98 const rgb_color kDiffColors[] = {
99 {0xb0, 0, 0, 0}, // '-', red
100 {0, 0x90, 0, 0}, // '+', green
101 {0x6a, 0x6a, 0x6a, 0} // '@@', dark grey
104 void Unicode2UTF8(int32 c, char **out);
107 inline bool
108 IsInitialUTF8Byte(uchar b)
110 return ((b & 0xC0) != 0x80);
114 void
115 Unicode2UTF8(int32 c, char **out)
117 char *s = *out;
119 ASSERT(c < 0x200000);
121 if (c < 0x80)
122 *(s++) = c;
123 else if (c < 0x800) {
124 *(s++) = 0xc0 | (c >> 6);
125 *(s++) = 0x80 | (c & 0x3f);
126 } else if (c < 0x10000) {
127 *(s++) = 0xe0 | (c >> 12);
128 *(s++) = 0x80 | ((c >> 6) & 0x3f);
129 *(s++) = 0x80 | (c & 0x3f);
130 } else if (c < 0x200000) {
131 *(s++) = 0xf0 | (c >> 18);
132 *(s++) = 0x80 | ((c >> 12) & 0x3f);
133 *(s++) = 0x80 | ((c >> 6) & 0x3f);
134 *(s++) = 0x80 | (c & 0x3f);
136 *out = s;
140 static bool
141 FilterHTMLTag(int32 &first, char **t, char *end)
143 const char *newlineTags[] = {
144 "br", "/p", "/div", "/table", "/tr",
145 NULL};
147 char *a = *t;
149 // check for some common entities (in ISO-Latin-1)
150 if (first == '&') {
151 // filter out and convert decimal values
152 if (a[1] == '#' && sscanf(a + 2, "%" B_SCNd32 ";", &first) == 1) {
153 t[0] += strchr(a, ';') - a;
154 return false;
157 const struct { const char *name; int32 code; } entities[] = {
158 // this list is sorted alphabetically to be binary searchable
159 // the current implementation doesn't do this, though
161 // "name" is the entity name,
162 // "code" is the corresponding unicode
163 {"AElig;", 0x00c6},
164 {"Aacute;", 0x00c1},
165 {"Acirc;", 0x00c2},
166 {"Agrave;", 0x00c0},
167 {"Aring;", 0x00c5},
168 {"Atilde;", 0x00c3},
169 {"Auml;", 0x00c4},
170 {"Ccedil;", 0x00c7},
171 {"Eacute;", 0x00c9},
172 {"Ecirc;", 0x00ca},
173 {"Egrave;", 0x00c8},
174 {"Euml;", 0x00cb},
175 {"Iacute;", 0x00cd},
176 {"Icirc;", 0x00ce},
177 {"Igrave;", 0x00cc},
178 {"Iuml;", 0x00cf},
179 {"Ntilde;", 0x00d1},
180 {"Oacute;", 0x00d3},
181 {"Ocirc;", 0x00d4},
182 {"Ograve;", 0x00d2},
183 {"Ouml;", 0x00d6},
184 {"Uacute;", 0x00da},
185 {"Ucirc;", 0x00db},
186 {"Ugrave;", 0x00d9},
187 {"Uuml;", 0x00dc},
188 {"aacute;", 0x00e1},
189 {"acirc;", 0x00e2},
190 {"aelig;", 0x00e6},
191 {"agrave;", 0x00e0},
192 {"amp;", '&'},
193 {"aring;", 0x00e5},
194 {"atilde;", 0x00e3},
195 {"auml;", 0x00e4},
196 {"ccedil;", 0x00e7},
197 {"copy;", 0x00a9},
198 {"eacute;", 0x00e9},
199 {"ecirc;", 0x00ea},
200 {"egrave;", 0x00e8},
201 {"euml;", 0x00eb},
202 {"gt;", '>'},
203 {"iacute;", 0x00ed},
204 {"icirc;", 0x00ee},
205 {"igrave;", 0x00ec},
206 {"iuml;", 0x00ef},
207 {"lt;", '<'},
208 {"nbsp;", ' '},
209 {"ntilde;", 0x00f1},
210 {"oacute;", 0x00f3},
211 {"ocirc;", 0x00f4},
212 {"ograve;", 0x00f2},
213 {"ouml;", 0x00f6},
214 {"quot;", '"'},
215 {"szlig;", 0x00f6},
216 {"uacute;", 0x00fa},
217 {"ucirc;", 0x00fb},
218 {"ugrave;", 0x00f9},
219 {"uuml;", 0x00fc},
220 {NULL, 0}
223 for (int32 i = 0; entities[i].name; i++) {
224 // entities are case-sensitive
225 int32 length = strlen(entities[i].name);
226 if (!strncmp(a + 1, entities[i].name, length)) {
227 t[0] += length; // note that the '&' is included here
228 first = entities[i].code;
229 return false;
234 // no tag to filter
235 if (first != '<')
236 return false;
238 a++;
240 // is the tag one of the newline tags?
242 bool newline = false;
243 for (int i = 0; newlineTags[i]; i++) {
244 int length = strlen(newlineTags[i]);
245 if (!strncasecmp(a, (char *)newlineTags[i], length) && !isalnum(a[length])) {
246 newline = true;
247 break;
251 // oh, it's not, so skip it!
253 if (!strncasecmp(a, "head", 4)) { // skip "head" completely
254 for (; a[0] && a < end; a++) {
255 // Find the end of the HEAD section, or the start of the BODY,
256 // which happens for some malformed spam.
257 if (strncasecmp (a, "</head", 6) == 0 ||
258 strncasecmp (a, "<body", 5) == 0)
259 break;
263 // skip until tag end
264 while (a[0] && a[0] != '>' && a < end)
265 a++;
267 t[0] = a;
269 if (newline) {
270 first = '\n';
271 return false;
274 return true;
278 /** Returns the type and length of the URL in the string if it is one.
279 * If the "url" string is specified, it will fill it with the complete
280 * URL that might differ from the one in the text itself (i.e. because
281 * of an prepended "http://").
284 static uint8
285 CheckForURL(const char *string, size_t &urlLength, BString *url = NULL)
287 const char *urlPrefixes[] = {
288 "http://",
289 "ftp://",
290 "shttp://",
291 "https://",
292 "finger://",
293 "telnet://",
294 "gopher://",
295 "news://",
296 "nntp://",
297 "file://",
298 NULL
302 // Search for URL prefix
304 uint8 type = 0;
305 for (const char **prefix = urlPrefixes; *prefix != 0; prefix++) {
306 if (!cistrncmp(string, *prefix, strlen(*prefix))) {
307 type = TYPE_URL;
308 break;
313 // Not a URL? check for "mailto:" or "www."
315 if (type == 0 && !cistrncmp(string, "mailto:", 7))
316 type = TYPE_MAILTO;
317 if (type == 0 && !strncmp(string, "www.", 4)) {
318 // this type will be special cased later (and a http:// is added
319 // for the enclosure address)
320 type = TYPE_URL;
322 if (type == 0) {
323 // check for valid eMail addresses
324 const char *at = strchr(string, '@');
325 if (at) {
326 const char *pos = string;
327 bool readName = false;
328 for (; pos < at; pos++) {
329 // ToDo: are these all allowed characters?
330 if (!isalnum(pos[0]) && pos[0] != '_' && pos[0] != '.' && pos[0] != '-')
331 break;
333 readName = true;
335 if (pos == at && readName)
336 type = TYPE_MAILTO;
340 if (type == 0)
341 return 0;
343 int32 index = strcspn(string, " \t<>)\"\\,\r\n");
345 // filter out some punctuation marks if they are the last character
346 char suffix = string[index - 1];
347 if (suffix == '.'
348 || suffix == ','
349 || suffix == '?'
350 || suffix == '!'
351 || suffix == ':'
352 || suffix == ';')
353 index--;
355 if (type == TYPE_URL) {
356 char *parenthesis = NULL;
358 // filter out a trailing ')' if there is no left parenthesis before
359 if (string[index - 1] == ')') {
360 char *parenthesis = strchr(string, '(');
361 if (parenthesis == NULL || parenthesis > string + index)
362 index--;
365 // filter out a trailing ']' if there is no left bracket before
366 if (parenthesis == NULL && string[index - 1] == ']') {
367 char *parenthesis = strchr(string, '[');
368 if (parenthesis == NULL || parenthesis > string + index)
369 index--;
373 if (url != NULL) {
374 // copy the address to the specified string
375 if (type == TYPE_URL && string[0] == 'w') {
376 // URL starts with "www.", so add the protocol to it
377 url->SetTo("http://");
378 url->Append(string, index);
379 } else if (type == TYPE_MAILTO && cistrncmp(string, "mailto:", 7)) {
380 // eMail address had no "mailto:" prefix
381 url->SetTo("mailto:");
382 url->Append(string, index);
383 } else
384 url->SetTo(string, index);
386 urlLength = index;
388 return type;
392 static void
393 CopyQuotes(const char *text, size_t length, char *outText, size_t &outLength)
395 // count qoute level (to be able to wrap quotes correctly)
397 const char *quote = QUOTE;
398 int32 level = 0;
399 for (size_t i = 0; i < length; i++) {
400 if (text[i] == quote[0])
401 level++;
402 else if (text[i] != ' ' && text[i] != '\t')
403 break;
406 // if there are too much quotes, try to preserve the quote color level
407 if (level > 10)
408 level = kNumQuoteColors * 3 + (level % kNumQuoteColors);
410 // copy the quotes to outText
412 const int32 quoteLength = strlen(QUOTE);
413 outLength = 0;
414 while (level-- > 0) {
415 strcpy(outText + outLength, QUOTE);
416 outLength += quoteLength;
421 int32
422 diff_mode(char c)
424 if (c == '+')
425 return 2;
426 if (c == '-')
427 return 1;
428 if (c == '@')
429 return 3;
430 if (c == ' ')
431 return 0;
433 // everything else ends the diff mode
434 return -1;
438 bool
439 is_quote_char(char c)
441 return c == '>' || c == '|';
445 /*! Fills the specified text_run_array with the correct values for the
446 specified text.
447 If "view" is NULL, it will assume that "line" lies on a line break,
448 if not, it will correctly retrieve the number of quotes the current
449 line already has.
451 void
452 FillInQuoteTextRuns(BTextView* view, quote_context* context, const char* line,
453 int32 length, const BFont& font, text_run_array* style, int32 maxStyles)
455 text_run* runs = style->runs;
456 int32 index = style->count;
457 bool begin;
458 int32 pos = 0;
459 int32 diffMode = 0;
460 bool inDiff = false;
461 bool wasDiff = false;
462 int32 level = 0;
464 // get index to the beginning of the current line
466 if (context != NULL) {
467 level = context->level;
468 diffMode = context->diff_mode;
469 begin = context->begin;
470 inDiff = context->in_diff;
471 wasDiff = context->was_diff;
472 } else if (view != NULL) {
473 int32 start, end;
474 view->GetSelection(&end, &end);
476 begin = view->TextLength() == 0
477 || view->ByteAt(view->TextLength() - 1) == '\n';
479 // the following line works only reliable when text wrapping is set to
480 // off; so the complicated version actually used here is necessary:
481 // start = view->OffsetAt(view->CurrentLine());
483 const char *text = view->Text();
485 if (!begin) {
486 // if the text is not the start of a new line, go back
487 // to the first character in the current line
488 for (start = end; start > 0; start--) {
489 if (text[start - 1] == '\n')
490 break;
494 // get number of nested qoutes for current line
496 if (!begin && start < end) {
497 begin = true;
498 // if there was no text in this line, there may come
499 // more nested quotes
501 diffMode = diff_mode(text[start]);
502 if (diffMode == 0) {
503 for (int32 i = start; i < end; i++) {
504 if (is_quote_char(text[i]))
505 level++;
506 else if (text[i] != ' ' && text[i] != '\t') {
507 begin = false;
508 break;
511 } else
512 inDiff = true;
514 if (begin) {
515 // skip leading spaces (tabs & newlines aren't allowed here)
516 while (line[pos] == ' ')
517 pos++;
520 } else
521 begin = true;
523 // set styles for all qoute levels in the text to be inserted
525 for (int32 pos = 0; pos < length;) {
526 int32 next;
527 if (begin && is_quote_char(line[pos])) {
528 begin = false;
530 while (pos < length && line[pos] != '\n') {
531 // insert style for each quote level
532 level++;
534 bool search = true;
535 for (next = pos + 1; next < length; next++) {
536 if ((search && is_quote_char(line[next]))
537 || line[next] == '\n')
538 break;
539 else if (search && line[next] != ' ' && line[next] != '\t')
540 search = false;
543 runs[index].offset = pos;
544 runs[index].font = font;
545 runs[index].color = level > 0
546 ? kQuoteColors[level % kNumQuoteColors] : kNormalTextColor;
548 pos = next;
549 if (++index >= maxStyles)
550 break;
552 } else {
553 if (begin) {
554 if (!inDiff) {
555 inDiff = !strncmp(&line[pos], "--- ", 4);
556 wasDiff = false;
558 if (inDiff) {
559 diffMode = diff_mode(line[pos]);
560 if (diffMode < 0) {
561 inDiff = false;
562 wasDiff = true;
567 runs[index].offset = pos;
568 runs[index].font = font;
569 if (wasDiff)
570 runs[index].color = kDiffColors[diff_mode('@') - 1];
571 else if (diffMode <= 0) {
572 runs[index].color = level > 0
573 ? kQuoteColors[level % kNumQuoteColors] : kNormalTextColor;
574 } else
575 runs[index].color = kDiffColors[diffMode - 1];
577 begin = false;
579 for (next = pos; next < length; next++) {
580 if (line[next] == '\n') {
581 begin = true;
582 wasDiff = false;
583 break;
587 pos = next;
588 index++;
591 if (pos < length)
592 begin = line[pos] == '\n';
594 if (begin) {
595 pos++;
596 level = 0;
597 wasDiff = false;
599 // skip one leading space (tabs & newlines aren't allowed here)
600 if (!inDiff && pos < length && line[pos] == ' ')
601 pos++;
604 if (index >= maxStyles)
605 break;
607 style->count = index;
609 if (context) {
610 // update context for next run
611 context->level = level;
612 context->diff_mode = diffMode;
613 context->begin = begin;
614 context->in_diff = inDiff;
615 context->was_diff = wasDiff;
620 // #pragma mark -
623 TextRunArray::TextRunArray(size_t entries)
625 fNumEntries(entries)
627 fArray = (text_run_array *)malloc(sizeof(int32) + sizeof(text_run) * entries);
628 if (fArray != NULL)
629 fArray->count = 0;
633 TextRunArray::~TextRunArray()
635 free(fArray);
639 // #pragma mark -
642 TContentView::TContentView(bool incoming, BFont* font,
643 bool showHeader, bool coloredQuotes)
645 BView("m_content", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
646 fFocus(false),
647 fIncoming(incoming)
649 SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
651 BGroupLayout* layout = new BGroupLayout(B_VERTICAL, 0);
652 SetLayout(layout);
654 fTextView = new TTextView(fIncoming, this, font, showHeader,
655 coloredQuotes);
657 BScrollView* scrollView = new BScrollView("", fTextView, 0, true, true);
658 scrollView->SetBorders(BControlLook::B_TOP_BORDER);
659 AddChild(scrollView);
663 void
664 TContentView::FindString(const char *str)
666 int32 finish;
667 int32 pass = 0;
668 int32 start = 0;
670 if (str == NULL)
671 return;
674 // Start from current selection or from the beginning of the pool
676 const char *text = fTextView->Text();
677 int32 count = fTextView->TextLength();
678 fTextView->GetSelection(&start, &finish);
679 if (start != finish)
680 start = finish;
681 if (!count || text == NULL)
682 return;
685 // Do the find
687 while (pass < 2) {
688 long found = -1;
689 char lc = tolower(str[0]);
690 char uc = toupper(str[0]);
691 for (long i = start; i < count; i++) {
692 if (text[i] == lc || text[i] == uc) {
693 const char *s = str;
694 const char *t = text + i;
695 while (*s && (tolower(*s) == tolower(*t))) {
696 s++;
697 t++;
699 if (*s == 0) {
700 found = i;
701 break;
707 // Select the text if it worked
709 if (found != -1) {
710 Window()->Activate();
711 fTextView->Select(found, found + strlen(str));
712 fTextView->ScrollToSelection();
713 fTextView->MakeFocus(true);
714 return;
716 else if (start) {
717 start = 0;
718 text = fTextView->Text();
719 count = fTextView->TextLength();
720 pass++;
721 } else {
722 beep();
723 return;
729 void
730 TContentView::Focus(bool focus)
732 if (fFocus != focus) {
733 fFocus = focus;
734 Draw(Frame());
739 void
740 TContentView::MessageReceived(BMessage *msg)
742 switch (msg->what) {
743 case CHANGE_FONT:
745 BFont *font;
746 msg->FindPointer("font", (void **)&font);
747 fTextView->UpdateFont(font);
748 fTextView->Invalidate(Bounds());
749 break;
752 case M_QUOTE:
754 int32 start, finish;
755 fTextView->GetSelection(&start, &finish);
756 fTextView->AddQuote(start, finish);
757 break;
759 case M_REMOVE_QUOTE:
761 int32 start, finish;
762 fTextView->GetSelection(&start, &finish);
763 fTextView->RemoveQuote(start, finish);
764 break;
767 case M_SIGNATURE:
769 if (fTextView->IsReaderThreadRunning()) {
770 // Do not add the signature until the reader thread
771 // is finished. Resubmit the message for later processing
772 Window()->PostMessage(msg);
773 break;
776 entry_ref ref;
777 msg->FindRef("ref", &ref);
779 BFile file(&ref, B_READ_ONLY);
780 if (file.InitCheck() == B_OK) {
781 int32 start, finish;
782 fTextView->GetSelection(&start, &finish);
784 off_t size;
785 file.GetSize(&size);
786 if (size > 32768) // safety against corrupt signatures
787 break;
789 char *signature = (char *)malloc(size);
790 if (signature == NULL)
791 break;
792 ssize_t bytesRead = file.Read(signature, size);
793 if (bytesRead < B_OK) {
794 free (signature);
795 break;
798 const char *text = fTextView->Text();
799 int32 length = fTextView->TextLength();
801 // reserve some empty lines before the signature
802 const char* newLines = "\n\n\n\n";
803 if (length && text[length - 1] == '\n')
804 newLines++;
806 fTextView->Select(length, length);
807 fTextView->Insert(newLines, strlen(newLines));
808 length += strlen(newLines);
810 // append the signature
811 fTextView->Select(length, length);
812 fTextView->Insert(signature, bytesRead);
813 fTextView->Select(length, length + bytesRead);
814 fTextView->ScrollToSelection();
816 // set the editing cursor position
817 fTextView->Select(length - 2 , length - 2);
818 fTextView->ScrollToSelection();
819 free (signature);
820 } else {
821 beep();
822 BAlert* alert = new BAlert("",
823 B_TRANSLATE("An error occurred trying to open this "
824 "signature."), B_TRANSLATE("Sorry"));
825 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
826 alert->Go();
828 break;
831 case M_FIND:
832 FindString(msg->FindString("findthis"));
833 break;
835 default:
836 BView::MessageReceived(msg);
841 // #pragma mark -
844 TTextView::TTextView(bool incoming, TContentView *view,
845 BFont *font, bool showHeader, bool coloredQuotes)
847 BTextView("", B_WILL_DRAW | B_NAVIGABLE),
849 fHeader(showHeader),
850 fColoredQuotes(coloredQuotes),
851 fReady(false),
852 fYankBuffer(NULL),
853 fLastPosition(-1),
854 fMail(NULL),
855 fFont(font),
856 fParent(view),
857 fStopLoading(false),
858 fThread(0),
859 fPanel(NULL),
860 fIncoming(incoming),
861 fSpellCheck(false),
862 fRaw(false),
863 fCursor(false),
864 fFirstSpellMark(NULL)
866 fStopSem = create_sem(1, "reader_sem");
867 SetStylable(true);
868 SetInsets(4, 4, 4, 4);
869 // TODO: have some font size related value here
870 // (ideally the same as in BTextControl, etc. from BControlLook)
872 fEnclosures = new BList();
874 // Enclosure pop up menu
875 fEnclosureMenu = new BPopUpMenu("Enclosure", false, false);
876 fEnclosureMenu->SetFont(be_plain_font);
877 fEnclosureMenu->AddItem(new BMenuItem(
878 B_TRANSLATE("Save attachment" B_UTF8_ELLIPSIS), new BMessage(M_SAVE)));
879 fEnclosureMenu->AddItem(new BMenuItem(B_TRANSLATE("Open attachment"),
880 new BMessage(M_OPEN)));
882 // Hyperlink pop up menu
883 fLinkMenu = new BPopUpMenu("Link", false, false);
884 fLinkMenu->SetFont(be_plain_font);
885 fLinkMenu->AddItem(new BMenuItem(B_TRANSLATE("Open this link"),
886 new BMessage(M_OPEN)));
887 fLinkMenu->AddItem(new BMenuItem(B_TRANSLATE("Copy link location"),
888 new BMessage(M_COPY)));
890 SetDoesUndo(true);
892 //Undo function
893 fUndoBuffer.On();
894 fInputMethodUndoBuffer.On();
895 fUndoState.replaced = false;
896 fUndoState.deleted = false;
897 fInputMethodUndoState.active = false;
898 fInputMethodUndoState.replace = false;
902 TTextView::~TTextView()
904 ClearList();
905 delete fPanel;
907 if (fYankBuffer)
908 free(fYankBuffer);
910 delete_sem(fStopSem);
914 void
915 TTextView::UpdateFont(const BFont* newFont)
917 fFont = *newFont;
919 // update the text run array safely with new font
920 text_run_array *runArray = RunArray(0, INT32_MAX);
921 for (int i = 0; i < runArray->count; i++)
922 runArray->runs[i].font = *newFont;
924 SetRunArray(0, INT32_MAX, runArray);
925 FreeRunArray(runArray);
929 void
930 TTextView::AttachedToWindow()
932 BTextView::AttachedToWindow();
933 fFont.SetSpacing(B_FIXED_SPACING);
934 SetFontAndColor(&fFont);
936 if (fMail != NULL) {
937 LoadMessage(fMail, false, NULL);
938 if (fIncoming)
939 MakeEditable(false);
944 void
945 TTextView::KeyDown(const char *key, int32 count)
947 char raw;
948 int32 end;
949 int32 start;
950 uint32 mods;
951 BMessage *msg;
952 int32 textLen = TextLength();
954 msg = Window()->CurrentMessage();
955 mods = msg->FindInt32("modifiers");
957 switch (key[0]) {
958 case B_HOME:
959 if (IsSelectable()) {
960 if (IsEditable())
961 BTextView::KeyDown(key, count);
962 else {
963 // scroll to the beginning
964 Select(0, 0);
965 ScrollToSelection();
968 break;
970 case B_END:
971 if (IsSelectable()) {
972 if (IsEditable())
973 BTextView::KeyDown(key, count);
974 else {
975 // scroll to the end
976 int32 length = TextLength();
977 Select(length, length);
978 ScrollToSelection();
981 break;
983 case 0x02: // ^b - back 1 char
984 if (IsSelectable()) {
985 GetSelection(&start, &end);
986 while (!IsInitialUTF8Byte(ByteAt(--start))) {
987 if (start < 0) {
988 start = 0;
989 break;
992 if (start >= 0) {
993 Select(start, start);
994 ScrollToSelection();
997 break;
999 case B_DELETE:
1000 if (IsSelectable()) {
1001 if ((key[0] == B_DELETE) || (mods & B_CONTROL_KEY)) {
1002 // ^d
1003 if (IsEditable()) {
1004 GetSelection(&start, &end);
1005 if (start != end)
1006 Delete();
1007 else {
1008 for (end = start + 1; !IsInitialUTF8Byte(ByteAt(end)); end++) {
1009 if (end > textLen) {
1010 end = textLen;
1011 break;
1014 Select(start, end);
1015 Delete();
1019 else
1020 Select(textLen, textLen);
1021 ScrollToSelection();
1023 break;
1025 case 0x05: // ^e - end of line
1026 if (IsSelectable() && (mods & B_CONTROL_KEY)) {
1027 if (CurrentLine() == CountLines() - 1)
1028 Select(TextLength(), TextLength());
1029 else {
1030 GoToLine(CurrentLine() + 1);
1031 GetSelection(&start, &end);
1032 Select(start - 1, start - 1);
1035 break;
1037 case 0x06: // ^f - forward 1 char
1038 if (IsSelectable()) {
1039 GetSelection(&start, &end);
1040 if (end > start)
1041 start = end;
1042 else {
1043 for (end = start + 1; !IsInitialUTF8Byte(ByteAt(end));
1044 end++) {
1045 if (end > textLen) {
1046 end = textLen;
1047 break;
1050 start = end;
1052 Select(start, start);
1053 ScrollToSelection();
1055 break;
1057 case 0x0e: // ^n - next line
1058 if (IsSelectable()) {
1059 raw = B_DOWN_ARROW;
1060 BTextView::KeyDown(&raw, 1);
1062 break;
1064 case 0x0f: // ^o - open line
1065 if (IsEditable()) {
1066 GetSelection(&start, &end);
1067 Delete();
1069 char newLine = '\n';
1070 Insert(&newLine, 1);
1071 Select(start, start);
1072 ScrollToSelection();
1074 break;
1076 case B_PAGE_UP:
1077 if (mods & B_CONTROL_KEY) { // ^k kill text from cursor to e-o-line
1078 if (IsEditable()) {
1079 GetSelection(&start, &end);
1080 if ((start != fLastPosition) && (fYankBuffer)) {
1081 free(fYankBuffer);
1082 fYankBuffer = NULL;
1084 fLastPosition = start;
1085 if (CurrentLine() < CountLines() - 1) {
1086 GoToLine(CurrentLine() + 1);
1087 GetSelection(&end, &end);
1088 end--;
1090 else
1091 end = TextLength();
1092 if (end < start)
1093 break;
1094 if (start == end)
1095 end++;
1096 Select(start, end);
1097 if (fYankBuffer) {
1098 fYankBuffer = (char *)realloc(fYankBuffer,
1099 strlen(fYankBuffer) + (end - start) + 1);
1100 GetText(start, end - start,
1101 &fYankBuffer[strlen(fYankBuffer)]);
1102 } else {
1103 fYankBuffer = (char *)malloc(end - start + 1);
1104 GetText(start, end - start, fYankBuffer);
1106 Delete();
1107 ScrollToSelection();
1109 break;
1112 BTextView::KeyDown(key, count);
1113 break;
1115 case 0x10: // ^p goto previous line
1116 if (IsSelectable()) {
1117 raw = B_UP_ARROW;
1118 BTextView::KeyDown(&raw, 1);
1120 break;
1122 case 0x19: // ^y yank text
1123 if (IsEditable() && fYankBuffer) {
1124 Delete();
1125 Insert(fYankBuffer);
1126 ScrollToSelection();
1128 break;
1130 default:
1131 BTextView::KeyDown(key, count);
1136 void
1137 TTextView::MakeFocus(bool focus)
1139 if (!focus) {
1140 // ToDo: can someone please translate this? Otherwise I will remove it - axeld.
1141 // MakeFocus(false) は、IM も Inactive になり、そのまま確定される。
1142 // しかしこの場合、input_server が B_INPUT_METHOD_EVENT(B_INPUT_METHOD_STOPPED)
1143 // を送ってこないまま矛盾してしまうので、やむを得ずここでつじつまあわせ処理している。
1144 fInputMethodUndoState.active = false;
1145 // fInputMethodUndoBufferに溜まっている最後のデータがK_INSERTEDなら(確定)正規のバッファへ追加
1146 if (fInputMethodUndoBuffer.CountItems() > 0) {
1147 KUndoItem *item = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1);
1148 if (item->History == K_INSERTED) {
1149 fUndoBuffer.MakeNewUndoItem();
1150 fUndoBuffer.AddUndo(item->RedoText, item->Length, item->Offset, item->History, item->CursorPos);
1151 fUndoBuffer.MakeNewUndoItem();
1153 fInputMethodUndoBuffer.MakeEmpty();
1156 BTextView::MakeFocus(focus);
1158 fParent->Focus(focus);
1162 void
1163 TTextView::MessageReceived(BMessage *msg)
1165 switch (msg->what) {
1166 case B_SIMPLE_DATA:
1168 if (fIncoming)
1169 break;
1171 BMessage message(REFS_RECEIVED);
1172 bool isEnclosure = false;
1173 bool inserted = false;
1175 off_t len = 0;
1176 int32 end;
1177 int32 start;
1179 int32 index = 0;
1180 entry_ref ref;
1181 while (msg->FindRef("refs", index++, &ref) == B_OK) {
1182 BFile file(&ref, B_READ_ONLY);
1183 if (file.InitCheck() == B_OK) {
1184 BNodeInfo node(&file);
1185 char type[B_FILE_NAME_LENGTH];
1186 node.GetType(type);
1188 off_t size = 0;
1189 file.GetSize(&size);
1191 if (!strncasecmp(type, "text/", 5) && size > 0) {
1192 len += size;
1193 char *text = (char *)malloc(size);
1194 if (text == NULL) {
1195 puts("no memory!");
1196 return;
1198 if (file.Read(text, size) < B_OK) {
1199 puts("could not read from file");
1200 free(text);
1201 continue;
1203 if (!inserted) {
1204 GetSelection(&start, &end);
1205 Delete();
1206 inserted = true;
1209 int32 offset = 0;
1210 for (int32 loop = 0; loop < size; loop++) {
1211 if (text[loop] == '\n') {
1212 Insert(&text[offset], loop - offset + 1);
1213 offset = loop + 1;
1214 } else if (text[loop] == '\r') {
1215 text[loop] = '\n';
1216 Insert(&text[offset], loop - offset + 1);
1217 if ((loop + 1 < size)
1218 && (text[loop + 1] == '\n'))
1219 loop++;
1220 offset = loop + 1;
1223 free(text);
1224 } else {
1225 isEnclosure = true;
1226 message.AddRef("refs", &ref);
1231 if (index == 1) {
1232 // message doesn't contain any refs - maybe the parent class likes it
1233 BTextView::MessageReceived(msg);
1234 break;
1237 if (inserted)
1238 Select(start, start + len);
1239 if (isEnclosure)
1240 Window()->PostMessage(&message, Window());
1241 break;
1244 case M_HEADER:
1245 msg->FindBool("header", &fHeader);
1246 SetText(NULL);
1247 LoadMessage(fMail, false, NULL);
1248 break;
1250 case M_RAW:
1251 StopLoad();
1253 msg->FindBool("raw", &fRaw);
1254 SetText(NULL);
1255 LoadMessage(fMail, false, NULL);
1256 break;
1258 case M_SELECT:
1259 if (IsSelectable())
1260 Select(0, TextLength());
1261 break;
1263 case M_SAVE:
1264 Save(msg);
1265 break;
1267 case B_NODE_MONITOR:
1269 int32 opcode;
1270 if (msg->FindInt32("opcode", &opcode) == B_NO_ERROR) {
1271 dev_t device;
1272 if (msg->FindInt32("device", &device) < B_OK)
1273 break;
1274 ino_t inode;
1275 if (msg->FindInt64("node", &inode) < B_OK)
1276 break;
1278 hyper_text *enclosure;
1279 for (int32 index = 0;
1280 (enclosure = (hyper_text *)fEnclosures->ItemAt(index++)) != NULL;) {
1281 if (device == enclosure->node.device
1282 && inode == enclosure->node.node) {
1283 if (opcode == B_ENTRY_REMOVED) {
1284 enclosure->saved = false;
1285 enclosure->have_ref = false;
1286 } else if (opcode == B_ENTRY_MOVED) {
1287 enclosure->ref.device = device;
1288 msg->FindInt64("to directory", &enclosure->ref.directory);
1290 const char *name;
1291 msg->FindString("name", &name);
1292 enclosure->ref.set_name(name);
1294 break;
1298 break;
1302 // Tracker has responded to a BMessage that was dragged out of
1303 // this email message. It has created a file for us, we just have to
1304 // put the stuff in it.
1306 case B_COPY_TARGET:
1308 BMessage data;
1309 if (msg->FindMessage("be:originator-data", &data) == B_OK) {
1310 entry_ref directory;
1311 const char *name;
1312 hyper_text *enclosure;
1314 if (data.FindPointer("enclosure", (void **)&enclosure) == B_OK
1315 && msg->FindString("name", &name) == B_OK
1316 && msg->FindRef("directory", &directory) == B_OK) {
1317 switch (enclosure->type) {
1318 case TYPE_ENCLOSURE:
1319 case TYPE_BE_ENCLOSURE:
1322 // Enclosure. Decode the data and write it out.
1324 BMessage saveMsg(M_SAVE);
1325 saveMsg.AddString("name", name);
1326 saveMsg.AddRef("directory", &directory);
1327 saveMsg.AddPointer("enclosure", enclosure);
1328 Save(&saveMsg, false);
1329 break;
1332 case TYPE_URL:
1334 const char *replyType;
1335 if (msg->FindString("be:filetypes", &replyType) != B_OK)
1336 // drag recipient didn't ask for any specific type,
1337 // create a bookmark file as default
1338 replyType = "application/x-vnd.Be-bookmark";
1340 BDirectory dir(&directory);
1341 BFile file(&dir, name, B_READ_WRITE);
1342 if (file.InitCheck() == B_OK) {
1343 if (strcmp(replyType, "application/x-vnd.Be-bookmark") == 0) {
1344 // we got a request to create a bookmark, stuff
1345 // it with the url attribute
1346 file.WriteAttr("META:url", B_STRING_TYPE, 0,
1347 enclosure->name, strlen(enclosure->name) + 1);
1348 } else if (strcasecmp(replyType, "text/plain") == 0) {
1349 // create a plain text file, stuff it with
1350 // the url as text
1351 file.Write(enclosure->name, strlen(enclosure->name));
1354 BNodeInfo fileInfo(&file);
1355 fileInfo.SetType(replyType);
1357 break;
1360 case TYPE_MAILTO:
1363 // Add some attributes to the already created
1364 // person file. Strip out the 'mailto:' if
1365 // possible.
1367 char *addrStart = enclosure->name;
1368 while (true) {
1369 if (*addrStart == ':') {
1370 addrStart++;
1371 break;
1374 if (*addrStart == '\0') {
1375 addrStart = enclosure->name;
1376 break;
1379 addrStart++;
1382 const char *replyType;
1383 if (msg->FindString("be:filetypes", &replyType) != B_OK)
1384 // drag recipient didn't ask for any specific type,
1385 // create a bookmark file as default
1386 replyType = "application/x-vnd.Be-bookmark";
1388 BDirectory dir(&directory);
1389 BFile file(&dir, name, B_READ_WRITE);
1390 if (file.InitCheck() == B_OK) {
1391 if (!strcmp(replyType, "application/x-person")) {
1392 // we got a request to create a bookmark, stuff
1393 // it with the address attribute
1394 file.WriteAttr("META:email", B_STRING_TYPE, 0,
1395 addrStart, strlen(enclosure->name) + 1);
1396 } else if (!strcasecmp(replyType, "text/plain")) {
1397 // create a plain text file, stuff it with the
1398 // email as text
1399 file.Write(addrStart, strlen(addrStart));
1402 BNodeInfo fileInfo(&file);
1403 fileInfo.SetType(replyType);
1405 break;
1408 } else {
1410 // Assume this is handled by BTextView...
1411 // (Probably drag clipping.)
1413 BTextView::MessageReceived(msg);
1416 break;
1419 case B_INPUT_METHOD_EVENT:
1421 int32 im_op;
1422 if (msg->FindInt32("be:opcode", &im_op) == B_OK){
1423 switch (im_op) {
1424 case B_INPUT_METHOD_STARTED:
1425 fInputMethodUndoState.replace = true;
1426 fInputMethodUndoState.active = true;
1427 break;
1428 case B_INPUT_METHOD_STOPPED:
1429 fInputMethodUndoState.active = false;
1430 if (fInputMethodUndoBuffer.CountItems() > 0) {
1431 KUndoItem *undo = fInputMethodUndoBuffer.ItemAt(
1432 fInputMethodUndoBuffer.CountItems() - 1);
1433 if (undo->History == K_INSERTED){
1434 fUndoBuffer.MakeNewUndoItem();
1435 fUndoBuffer.AddUndo(undo->RedoText, undo->Length,
1436 undo->Offset, undo->History, undo->CursorPos);
1437 fUndoBuffer.MakeNewUndoItem();
1439 fInputMethodUndoBuffer.MakeEmpty();
1441 break;
1442 case B_INPUT_METHOD_CHANGED:
1443 fInputMethodUndoState.active = true;
1444 break;
1445 case B_INPUT_METHOD_LOCATION_REQUEST:
1446 fInputMethodUndoState.active = true;
1447 break;
1450 BTextView::MessageReceived(msg);
1451 break;
1454 case M_REDO:
1455 Redo();
1456 break;
1458 default:
1459 BTextView::MessageReceived(msg);
1464 void
1465 TTextView::MouseDown(BPoint where)
1467 if (IsEditable()) {
1468 BPoint point;
1469 uint32 buttons;
1470 GetMouse(&point, &buttons);
1471 if (gDictCount && (buttons == B_SECONDARY_MOUSE_BUTTON)) {
1472 int32 offset, start, end, length;
1473 const char *text = Text();
1474 offset = OffsetAt(where);
1475 if (isalpha(text[offset])) {
1476 length = TextLength();
1478 //Find start and end of word
1479 //FindSpellBoundry(length, offset, &start, &end);
1481 char c;
1482 bool isAlpha, isApost, isCap;
1483 int32 first;
1485 for (first = offset;
1486 (first >= 0) && (((c = text[first]) == '\'') || isalpha(c));
1487 first--) {}
1488 isCap = isupper(text[++first]);
1490 for (start = offset, c = text[start], isAlpha = isalpha(c), isApost = (c=='\'');
1491 (start >= 0) && (isAlpha || (isApost
1492 && (((c = text[start+1]) != 's') || !isCap) && isalpha(c)
1493 && isalpha(text[start-1])));
1494 start--, c = text[start], isAlpha = isalpha(c), isApost = (c == '\'')) {}
1495 start++;
1497 for (end = offset, c = text[end], isAlpha = isalpha(c), isApost = (c == '\'');
1498 (end < length) && (isAlpha || (isApost
1499 && (((c = text[end + 1]) != 's') || !isCap) && isalpha(c)));
1500 end++, c = text[end], isAlpha = isalpha(c), isApost = (c == '\'')) {}
1502 length = end - start;
1503 BString srcWord;
1504 srcWord.SetTo(text + start, length);
1506 bool foundWord = false;
1507 BList matches;
1508 BString *string;
1510 BMenuItem *menuItem;
1511 BPopUpMenu menu("Words", false, false);
1513 for (int32 i = 0; i < gDictCount; i++)
1514 gWords[i]->FindBestMatches(&matches,
1515 srcWord.String());
1517 if (matches.CountItems()) {
1518 sort_word_list(&matches, srcWord.String());
1519 for (int32 i = 0; (string = (BString *)matches.ItemAt(i)) != NULL; i++) {
1520 menu.AddItem((menuItem = new BMenuItem(string->String(), NULL)));
1521 if (!strcasecmp(string->String(), srcWord.String())) {
1522 menuItem->SetEnabled(false);
1523 foundWord = true;
1525 delete string;
1527 } else {
1528 menuItem = new BMenuItem(B_TRANSLATE("No matches"), NULL);
1529 menuItem->SetEnabled(false);
1530 menu.AddItem(menuItem);
1533 BMenuItem *addItem = NULL;
1534 if (!foundWord && gUserDict >= 0) {
1535 menu.AddSeparatorItem();
1536 addItem = new BMenuItem(B_TRANSLATE("Add"), NULL);
1537 menu.AddItem(addItem);
1540 point = ConvertToScreen(where);
1541 if ((menuItem = menu.Go(point, false, false)) != NULL) {
1542 if (menuItem == addItem) {
1543 BString newItem(srcWord.String());
1544 newItem << "\n";
1545 gWords[gUserDict]->InitIndex();
1546 gExactWords[gUserDict]->InitIndex();
1547 gUserDictFile->Write(newItem.String(), newItem.Length());
1548 gWords[gUserDict]->BuildIndex();
1549 gExactWords[gUserDict]->BuildIndex();
1551 if (fSpellCheck)
1552 CheckSpelling(0, TextLength());
1553 } else {
1554 int32 len = strlen(menuItem->Label());
1555 Select(start, start);
1556 Delete(start, end);
1557 Insert(start, menuItem->Label(), len);
1558 Select(start+len, start+len);
1562 return;
1563 } else if (fSpellCheck && IsEditable()) {
1564 int32 start, end;
1566 GetSelection(&start, &end);
1567 FindSpellBoundry(1, start, &start, &end);
1568 CheckSpelling(start, end);
1570 } else {
1571 // is not editable, look for enclosures/links
1573 int32 clickOffset = OffsetAt(where);
1574 int32 items = fEnclosures->CountItems();
1575 for (int32 loop = 0; loop < items; loop++) {
1576 hyper_text *enclosure = (hyper_text*) fEnclosures->ItemAt(loop);
1577 if (clickOffset < enclosure->text_start || clickOffset >= enclosure->text_end)
1578 continue;
1581 // The user is clicking on this attachment
1584 int32 start;
1585 int32 finish;
1586 Select(enclosure->text_start, enclosure->text_end);
1587 GetSelection(&start, &finish);
1588 Window()->UpdateIfNeeded();
1590 bool drag = false;
1591 bool held = false;
1592 uint32 buttons = 0;
1593 if (Window()->CurrentMessage()) {
1594 Window()->CurrentMessage()->FindInt32("buttons",
1595 (int32 *) &buttons);
1599 // If this is the primary button, wait to see if the user is going
1600 // to single click, hold, or drag.
1602 if (buttons != B_SECONDARY_MOUSE_BUTTON) {
1603 BPoint point = where;
1604 bigtime_t popupDelay;
1605 get_click_speed(&popupDelay);
1606 popupDelay *= 2;
1607 popupDelay += system_time();
1608 while (buttons && abs((int)(point.x - where.x)) < 4
1609 && abs((int)(point.y - where.y)) < 4
1610 && system_time() < popupDelay) {
1611 snooze(10000);
1612 GetMouse(&point, &buttons);
1615 if (system_time() < popupDelay) {
1617 // The user either dragged this or released the button.
1618 // check if it was dragged.
1620 if (!(abs((int)(point.x - where.x)) < 4
1621 && abs((int)(point.y - where.y)) < 4) && buttons)
1622 drag = true;
1623 } else {
1625 // The user held the button down.
1627 held = true;
1632 // If the user has right clicked on this menu,
1633 // or held the button down on it for a while,
1634 // pop up a context menu.
1636 if (buttons == B_SECONDARY_MOUSE_BUTTON || held) {
1638 // Right mouse click... Display a menu
1640 BPoint point = where;
1641 ConvertToScreen(&point);
1643 BMenuItem *item;
1644 if ((enclosure->type != TYPE_ENCLOSURE)
1645 && (enclosure->type != TYPE_BE_ENCLOSURE))
1646 item = fLinkMenu->Go(point, true);
1647 else
1648 item = fEnclosureMenu->Go(point, true);
1650 BMessage *msg;
1651 if (item && (msg = item->Message()) != NULL) {
1652 if (msg->what == M_SAVE) {
1653 if (fPanel)
1654 fPanel->SetEnclosure(enclosure);
1655 else {
1656 fPanel = new TSavePanel(enclosure, this);
1657 fPanel->Window()->Show();
1659 } else if (msg->what == M_COPY) {
1660 // copy link location to clipboard
1662 if (be_clipboard->Lock()) {
1663 be_clipboard->Clear();
1665 BMessage *clip;
1666 if ((clip = be_clipboard->Data()) != NULL) {
1667 clip->AddData("text/plain", B_MIME_TYPE,
1668 enclosure->name, strlen(enclosure->name));
1669 be_clipboard->Commit();
1671 be_clipboard->Unlock();
1673 } else
1674 Open(enclosure);
1676 } else {
1678 // Left button. If the user single clicks, open this link.
1679 // Otherwise, initiate a drag.
1681 if (drag) {
1682 BMessage dragMessage(B_SIMPLE_DATA);
1683 dragMessage.AddInt32("be:actions", B_COPY_TARGET);
1684 dragMessage.AddString("be:types", B_FILE_MIME_TYPE);
1685 switch (enclosure->type) {
1686 case TYPE_BE_ENCLOSURE:
1687 case TYPE_ENCLOSURE:
1689 // Attachment. The type is specified in the message.
1691 dragMessage.AddString("be:types", B_FILE_MIME_TYPE);
1692 dragMessage.AddString("be:filetypes",
1693 enclosure->content_type ? enclosure->content_type : "");
1694 dragMessage.AddString("be:clip_name", enclosure->name);
1695 break;
1697 case TYPE_URL:
1699 // URL. The user can drag it into the tracker to
1700 // create a bookmark file.
1702 dragMessage.AddString("be:types", B_FILE_MIME_TYPE);
1703 dragMessage.AddString("be:filetypes",
1704 "application/x-vnd.Be-bookmark");
1705 dragMessage.AddString("be:filetypes", "text/plain");
1706 dragMessage.AddString("be:clip_name", "Bookmark");
1708 dragMessage.AddString("be:url", enclosure->name);
1709 break;
1711 case TYPE_MAILTO:
1713 // Mailto address. The user can drag it into the
1714 // tracker to create a people file.
1716 dragMessage.AddString("be:types", B_FILE_MIME_TYPE);
1717 dragMessage.AddString("be:filetypes",
1718 "application/x-person");
1719 dragMessage.AddString("be:filetypes", "text/plain");
1720 dragMessage.AddString("be:clip_name", "Person");
1722 dragMessage.AddString("be:email", enclosure->name);
1723 break;
1725 default:
1727 // Otherwise it doesn't have a type that I know how
1728 // to save. It won't have any types and if any
1729 // program wants to accept it, more power to them.
1730 // (tracker won't.)
1732 dragMessage.AddString("be:clip_name", "Hyperlink");
1735 BMessage data;
1736 data.AddPointer("enclosure", enclosure);
1737 dragMessage.AddMessage("be:originator-data", &data);
1739 BRegion selectRegion;
1740 GetTextRegion(start, finish, &selectRegion);
1741 DragMessage(&dragMessage, selectRegion.Frame(), this);
1742 } else {
1744 // User Single clicked on the attachment. Open it.
1746 Open(enclosure);
1749 return;
1752 BTextView::MouseDown(where);
1756 void
1757 TTextView::MouseMoved(BPoint where, uint32 code, const BMessage *msg)
1759 int32 start = OffsetAt(where);
1761 for (int32 loop = fEnclosures->CountItems(); loop-- > 0;) {
1762 hyper_text *enclosure = (hyper_text *)fEnclosures->ItemAt(loop);
1763 if ((start >= enclosure->text_start) && (start < enclosure->text_end)) {
1764 if (!fCursor)
1765 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
1766 fCursor = true;
1767 return;
1771 if (fCursor) {
1772 SetViewCursor(B_CURSOR_I_BEAM);
1773 fCursor = false;
1776 BTextView::MouseMoved(where, code, msg);
1780 void
1781 TTextView::ClearList()
1783 hyper_text *enclosure;
1784 while ((enclosure = (hyper_text *)fEnclosures->FirstItem()) != NULL) {
1785 fEnclosures->RemoveItem(enclosure);
1787 if (enclosure->name)
1788 free(enclosure->name);
1789 if (enclosure->content_type)
1790 free(enclosure->content_type);
1791 if (enclosure->encoding)
1792 free(enclosure->encoding);
1793 if (enclosure->have_ref && !enclosure->saved) {
1794 BEntry entry(&enclosure->ref);
1795 entry.Remove();
1798 watch_node(&enclosure->node, B_STOP_WATCHING, this);
1799 free(enclosure);
1804 void
1805 TTextView::LoadMessage(BEmailMessage *mail, bool quoteIt, const char *text)
1807 StopLoad();
1809 fMail = mail;
1811 ClearList();
1813 MakeSelectable(true);
1814 MakeEditable(false);
1815 if (text)
1816 Insert(text, strlen(text));
1818 //attr_info attrInfo;
1819 TTextView::Reader *reader = new TTextView::Reader(fHeader, fRaw, quoteIt, fIncoming,
1820 text != NULL, true,
1821 // I removed the following, because I absolutely can't imagine why it's
1822 // there (the mail kit should be able to deal with non-compliant mails)
1823 // -- axeld.
1824 // fFile->GetAttrInfo(B_MAIL_ATTR_MIME, &attrInfo) == B_OK,
1825 this, mail, fEnclosures, fStopSem);
1827 resume_thread(fThread = spawn_thread(Reader::Run, "reader", B_NORMAL_PRIORITY, reader));
1831 void
1832 TTextView::Open(hyper_text *enclosure)
1834 switch (enclosure->type) {
1835 case TYPE_URL:
1837 const struct {const char *urlType, *handler; } handlerTable[] = {
1838 {"http", B_URL_HTTP},
1839 {"https", B_URL_HTTPS},
1840 {"ftp", B_URL_FTP},
1841 {"gopher", B_URL_GOPHER},
1842 {"mailto", B_URL_MAILTO},
1843 {"news", B_URL_NEWS},
1844 {"nntp", B_URL_NNTP},
1845 {"telnet", B_URL_TELNET},
1846 {"rlogin", B_URL_RLOGIN},
1847 {"tn3270", B_URL_TN3270},
1848 {"wais", B_URL_WAIS},
1849 {"file", B_URL_FILE},
1850 {NULL, NULL}
1852 const char *handlerToLaunch = NULL;
1854 const char *colonPos = strchr(enclosure->name, ':');
1855 if (colonPos) {
1856 int urlTypeLength = colonPos - enclosure->name;
1858 for (int32 index = 0; handlerTable[index].urlType; index++) {
1859 if (!strncasecmp(enclosure->name,
1860 handlerTable[index].urlType, urlTypeLength)) {
1861 handlerToLaunch = handlerTable[index].handler;
1862 break;
1866 if (handlerToLaunch) {
1867 entry_ref appRef;
1868 if (be_roster->FindApp(handlerToLaunch, &appRef) != B_OK)
1869 handlerToLaunch = NULL;
1871 if (!handlerToLaunch)
1872 handlerToLaunch = "application/x-vnd.Be-Bookmark";
1874 status_t result = be_roster->Launch(handlerToLaunch, 1, &enclosure->name);
1875 if (result != B_NO_ERROR && result != B_ALREADY_RUNNING) {
1876 beep();
1877 BAlert* alert = new BAlert("",
1878 B_TRANSLATE("There is no installed handler for "
1879 "URL links."), B_TRANSLATE("Sorry"));
1880 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1881 alert->Go();
1883 break;
1886 case TYPE_MAILTO:
1887 if (be_roster->Launch(B_MAIL_TYPE, 1, &enclosure->name) < B_OK) {
1888 char *argv[] = {(char *)"Mail", enclosure->name};
1889 be_app->ArgvReceived(2, argv);
1891 break;
1893 case TYPE_ENCLOSURE:
1894 case TYPE_BE_ENCLOSURE:
1895 if (!enclosure->have_ref) {
1896 BPath path;
1897 if (find_directory(B_SYSTEM_TEMP_DIRECTORY, &path) == B_NO_ERROR) {
1898 BDirectory dir(path.Path());
1899 if (dir.InitCheck() == B_NO_ERROR) {
1900 char name[B_FILE_NAME_LENGTH];
1901 char baseName[B_FILE_NAME_LENGTH];
1902 strcpy(baseName, enclosure->name ? enclosure->name : "enclosure");
1903 strcpy(name, baseName);
1904 for (int32 index = 0; dir.Contains(name); index++)
1905 sprintf(name, "%s_%" B_PRId32, baseName, index);
1907 BEntry entry(path.Path());
1908 entry_ref ref;
1909 entry.GetRef(&ref);
1911 BMessage save(M_SAVE);
1912 save.AddRef("directory", &ref);
1913 save.AddString("name", name);
1914 save.AddPointer("enclosure", enclosure);
1915 if (Save(&save) != B_NO_ERROR)
1916 break;
1917 enclosure->saved = false;
1922 BMessenger tracker("application/x-vnd.Be-TRAK");
1923 if (tracker.IsValid()) {
1924 BMessage openMsg(B_REFS_RECEIVED);
1925 openMsg.AddRef("refs", &enclosure->ref);
1926 tracker.SendMessage(&openMsg);
1928 break;
1933 status_t
1934 TTextView::Save(BMessage *msg, bool makeNewFile)
1936 const char *name;
1937 entry_ref ref;
1938 BFile file;
1939 BPath path;
1940 hyper_text *enclosure;
1941 status_t result = B_NO_ERROR;
1942 char entry_name[B_FILE_NAME_LENGTH];
1944 msg->FindString("name", &name);
1945 msg->FindRef("directory", &ref);
1946 msg->FindPointer("enclosure", (void **)&enclosure);
1948 BDirectory dir;
1949 dir.SetTo(&ref);
1950 result = dir.InitCheck();
1952 if (result == B_OK) {
1953 if (makeNewFile) {
1955 // Search for the file and delete it if it already exists.
1956 // (It may not, that's ok.)
1958 BEntry entry;
1959 if (dir.FindEntry(name, &entry) == B_NO_ERROR)
1960 entry.Remove();
1962 if ((enclosure->have_ref) && (!enclosure->saved)) {
1963 entry.SetTo(&enclosure->ref);
1966 // Added true arg and entry_name so MoveTo clobbers as
1967 // before. This may not be the correct behaviour, but
1968 // it's the preserved behaviour.
1970 entry.GetName(entry_name);
1971 result = entry.MoveTo(&dir, entry_name, true);
1972 if (result == B_NO_ERROR) {
1973 entry.Rename(name);
1974 entry.GetRef(&enclosure->ref);
1975 entry.GetNodeRef(&enclosure->node);
1976 enclosure->saved = true;
1977 return result;
1981 if (result == B_NO_ERROR) {
1982 result = dir.CreateFile(name, &file);
1983 if (result == B_NO_ERROR && enclosure->content_type) {
1984 char type[B_MIME_TYPE_LENGTH];
1986 if (!strcasecmp(enclosure->content_type, "message/rfc822"))
1987 strcpy(type, "text/x-email");
1988 else if (!strcasecmp(enclosure->content_type, "message/delivery-status"))
1989 strcpy(type, "text/plain");
1990 else
1991 strcpy(type, enclosure->content_type);
1993 BNodeInfo info(&file);
1994 info.SetType(type);
1997 } else {
1999 // This file was dragged into the tracker or desktop. The file
2000 // already exists.
2002 result = file.SetTo(&dir, name, B_WRITE_ONLY);
2006 if (enclosure->component == NULL)
2007 result = B_ERROR;
2009 if (result == B_NO_ERROR) {
2011 // Write the data
2013 enclosure->component->GetDecodedData(&file);
2015 BEntry entry;
2016 dir.FindEntry(name, &entry);
2017 entry.GetRef(&enclosure->ref);
2018 enclosure->have_ref = true;
2019 enclosure->saved = true;
2020 entry.GetPath(&path);
2021 update_mime_info(path.Path(), false, true,
2022 !cistrcmp("application/octet-stream", enclosure->content_type ? enclosure->content_type : B_EMPTY_STRING));
2023 entry.GetNodeRef(&enclosure->node);
2024 watch_node(&enclosure->node, B_WATCH_NAME, this);
2027 if (result != B_NO_ERROR) {
2028 beep();
2029 BAlert* alert = new BAlert("", B_TRANSLATE("An error occurred trying to save "
2030 "the attachment."), B_TRANSLATE("Sorry"));
2031 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2032 alert->Go();
2035 return result;
2039 void
2040 TTextView::StopLoad()
2042 Window()->Unlock();
2044 thread_info info;
2045 if (fThread != 0 && get_thread_info(fThread, &info) == B_NO_ERROR) {
2046 fStopLoading = true;
2047 acquire_sem(fStopSem);
2048 int32 result;
2049 wait_for_thread(fThread, &result);
2050 fThread = 0;
2051 release_sem(fStopSem);
2052 fStopLoading = false;
2055 Window()->Lock();
2059 bool
2060 TTextView::IsReaderThreadRunning()
2062 if (fThread == 0)
2063 return false;
2065 thread_info info;
2066 for (int i = 5; i > 0; i--, usleep(100000))
2067 if (get_thread_info(fThread, &info) != B_OK)
2068 return false;
2069 return true;
2073 void
2074 TTextView::AddAsContent(BEmailMessage *mail, bool wrap, uint32 charset, mail_encoding encoding)
2076 if (mail == NULL)
2077 return;
2079 int32 textLength = TextLength();
2080 const char *text = Text();
2082 BTextMailComponent *body = mail->Body();
2083 if (body == NULL) {
2084 if (mail->SetBody(body = new BTextMailComponent()) < B_OK)
2085 return;
2087 body->SetEncoding(encoding, charset);
2089 // Just add the text as a whole if we can, or ...
2090 if (!wrap) {
2091 body->AppendText(text);
2092 return;
2095 // ... do word wrapping.
2097 BWindow *window = Window();
2098 char *saveText = strdup(text);
2099 BRect saveTextRect = TextRect();
2101 // do this before we start messing with the fonts
2102 // the user will never know...
2103 window->DisableUpdates();
2104 Hide();
2105 BScrollBar *vScroller = ScrollBar(B_VERTICAL);
2106 BScrollBar *hScroller = ScrollBar(B_HORIZONTAL);
2107 if (vScroller != NULL)
2108 vScroller->SetTarget((BView *)NULL);
2109 if (hScroller != NULL)
2110 hScroller->SetTarget((BView *)NULL);
2112 // Temporarily set the font to a fixed width font for line wrapping
2113 // calculations. If the font doesn't have as many of the symbols as
2114 // the preferred font, go back to using the user's preferred font.
2116 bool *boolArray;
2117 int missingCharactersFixedWidth = 0;
2118 int missingCharactersPreferredFont = 0;
2119 int32 numberOfCharacters;
2121 numberOfCharacters = BString(text).CountChars();
2122 if (numberOfCharacters > 0
2123 && (boolArray = (bool *)malloc(sizeof(bool) * numberOfCharacters)) != NULL) {
2124 memset(boolArray, 0, sizeof (bool) * numberOfCharacters);
2125 be_fixed_font->GetHasGlyphs(text, numberOfCharacters, boolArray);
2126 for (int i = 0; i < numberOfCharacters; i++) {
2127 if (!boolArray[i])
2128 missingCharactersFixedWidth += 1;
2131 memset(boolArray, 0, sizeof (bool) * numberOfCharacters);
2132 fFont.GetHasGlyphs(text, numberOfCharacters, boolArray);
2133 for (int i = 0; i < numberOfCharacters; i++) {
2134 if (!boolArray[i])
2135 missingCharactersPreferredFont += 1;
2138 free(boolArray);
2141 if (missingCharactersFixedWidth > missingCharactersPreferredFont)
2142 SetFontAndColor(0, textLength, &fFont);
2143 else // All things being equal, the fixed font is better for wrapping.
2144 SetFontAndColor(0, textLength, be_fixed_font);
2146 // calculate a text rect that is 72 columns wide
2147 BRect newTextRect = saveTextRect;
2148 newTextRect.right = newTextRect.left + be_fixed_font->StringWidth("m") * 72;
2149 SetTextRect(newTextRect);
2151 // hard-wrap, based on TextView's soft-wrapping
2152 int32 numLines = CountLines();
2153 bool spaceMoved = false;
2154 char *content = (char *)malloc(textLength + numLines * 72); // more we'll ever need
2155 if (content != NULL) {
2156 int32 contentLength = 0;
2158 for (int32 i = 0; i < numLines; i++) {
2159 int32 startOffset = OffsetAt(i);
2160 if (spaceMoved) {
2161 startOffset++;
2162 spaceMoved = false;
2164 int32 endOffset = OffsetAt(i + 1);
2165 int32 lineLength = endOffset - startOffset;
2167 // quick hack to not break URLs into several parts
2168 for (int32 pos = startOffset; pos < endOffset; pos++) {
2169 size_t urlLength;
2170 uint8 type = CheckForURL(text + pos, urlLength);
2171 if (type != 0)
2172 pos += urlLength;
2174 if (pos > endOffset) {
2175 // find first break character after the URL
2176 for (; text[pos]; pos++) {
2177 if (isalnum(text[pos]) || isspace(text[pos]))
2178 break;
2180 if (text[pos] && isspace(text[pos]) && text[pos] != '\n')
2181 pos++;
2183 endOffset += pos - endOffset;
2184 lineLength = endOffset - startOffset;
2186 // insert a newline (and the same number of quotes) after the
2187 // URL to make sure the rest of the text is properly wrapped
2189 char buffer[64];
2190 if (text[pos] == '\n')
2191 buffer[0] = '\0';
2192 else
2193 strcpy(buffer, "\n");
2195 size_t quoteLength;
2196 CopyQuotes(text + startOffset, lineLength, buffer + strlen(buffer), quoteLength);
2198 Insert(pos, buffer, strlen(buffer));
2199 numLines = CountLines();
2200 text = Text();
2201 i++;
2204 if (text[endOffset - 1] != ' '
2205 && text[endOffset - 1] != '\n'
2206 && text[endOffset] == ' ') {
2207 // make sure spaces will be part of this line
2208 endOffset++;
2209 lineLength++;
2210 spaceMoved = true;
2213 memcpy(content + contentLength, text + startOffset, lineLength);
2214 contentLength += lineLength;
2216 // add a newline to every line except for the ones
2217 // that already end in newlines, and the last line
2218 if ((text[endOffset - 1] != '\n') && (i < numLines - 1)) {
2219 content[contentLength++] = '\n';
2221 // copy quote level of the first line
2222 size_t quoteLength;
2223 CopyQuotes(text + startOffset, lineLength, content + contentLength, quoteLength);
2224 contentLength += quoteLength;
2227 content[contentLength] = '\0';
2229 body->AppendText(content);
2230 free(content);
2233 // reset the text rect and font
2234 SetTextRect(saveTextRect);
2235 SetText(saveText);
2236 free(saveText);
2237 SetFontAndColor(0, textLength, &fFont);
2239 // should be OK to hook these back up now
2240 if (vScroller != NULL)
2241 vScroller->SetTarget(this);
2242 if (hScroller != NULL)
2243 hScroller->SetTarget(this);
2245 Show();
2246 window->EnableUpdates();
2250 // #pragma mark -
2253 TTextView::Reader::Reader(bool header, bool raw, bool quote, bool incoming,
2254 bool stripHeader, bool mime, TTextView *view, BEmailMessage *mail,
2255 BList *list, sem_id sem)
2257 fHeader(header),
2258 fRaw(raw),
2259 fQuote(quote),
2260 fIncoming(incoming),
2261 fStripHeader(stripHeader),
2262 fMime(mime),
2263 fView(view),
2264 fMail(mail),
2265 fEnclosures(list),
2266 fStopSem(sem)
2271 bool
2272 TTextView::Reader::ParseMail(BMailContainer *container,
2273 BTextMailComponent *ignore)
2275 int32 count = 0;
2276 for (int32 i = 0; i < container->CountComponents(); i++) {
2277 if (fView->fStopLoading)
2278 return false;
2280 BMailComponent *component;
2281 if ((component = container->GetComponent(i)) == NULL) {
2282 if (fView->fStopLoading)
2283 return false;
2285 hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text));
2286 if (enclosure == NULL)
2287 return false;
2289 memset(enclosure, 0, sizeof(hyper_text));
2291 enclosure->type = TYPE_ENCLOSURE;
2293 const char *name = "\n<Attachment: could not handle>\n";
2295 fView->GetSelection(&enclosure->text_start, &enclosure->text_end);
2296 enclosure->text_start++;
2297 enclosure->text_end += strlen(name) - 1;
2299 Insert(name, strlen(name), true);
2300 fEnclosures->AddItem(enclosure);
2301 continue;
2304 count++;
2305 if (component == ignore)
2306 continue;
2308 if (component->ComponentType() == B_MAIL_MULTIPART_CONTAINER) {
2309 BMIMEMultipartMailContainer *c = dynamic_cast<BMIMEMultipartMailContainer *>(container->GetComponent(i));
2310 ASSERT(c != NULL);
2312 if (!ParseMail(c, ignore))
2313 count--;
2314 } else if (fIncoming) {
2315 hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text));
2316 if (enclosure == NULL)
2317 return false;
2319 memset(enclosure, 0, sizeof(hyper_text));
2321 enclosure->type = TYPE_ENCLOSURE;
2322 enclosure->component = component;
2324 BString name;
2325 char fileName[B_FILE_NAME_LENGTH];
2326 strcpy(fileName, "untitled");
2327 if (BMailAttachment *attachment = dynamic_cast <BMailAttachment *> (component))
2328 attachment->FileName(fileName);
2330 BPath path(fileName);
2331 enclosure->name = strdup(path.Leaf());
2333 BMimeType type;
2334 component->MIMEType(&type);
2335 enclosure->content_type = strdup(type.Type());
2337 char typeDescription[B_MIME_TYPE_LENGTH];
2338 if (type.GetShortDescription(typeDescription) != B_OK)
2339 strcpy(typeDescription, type.Type() ? type.Type() : B_EMPTY_STRING);
2341 name = "\n<";
2342 name.Append(B_TRANSLATE_COMMENT("Enclosure: %name% (Type: %type%)",
2343 "Don't translate the variables %name% and %type%."));
2344 name.Append(">\n");
2345 name.ReplaceFirst("%name%", enclosure->name);
2346 name.ReplaceFirst("%type%", typeDescription);
2348 fView->GetSelection(&enclosure->text_start, &enclosure->text_end);
2349 enclosure->text_start++;
2350 enclosure->text_end += strlen(name.String()) - 1;
2352 Insert(name.String(), name.Length(), true);
2353 fEnclosures->AddItem(enclosure);
2355 // default:
2356 // {
2357 // PlainTextBodyComponent *body = dynamic_cast<PlainTextBodyComponent *>(container->GetComponent(i));
2358 // const char *text;
2359 // if (body && (text = body->Text()) != NULL)
2360 // Insert(text, strlen(text), false);
2361 // }
2363 return count > 0;
2367 bool
2368 TTextView::Reader::Process(const char *data, int32 data_len, bool isHeader)
2370 char line[522];
2371 int32 count = 0;
2373 for (int32 loop = 0; loop < data_len; loop++) {
2374 if (fView->fStopLoading)
2375 return false;
2377 if (fQuote && (!loop || (loop && data[loop - 1] == '\n'))) {
2378 strcpy(&line[count], QUOTE);
2379 count += strlen(QUOTE);
2381 if (!fRaw && fIncoming && (loop < data_len - 7)) {
2382 size_t urlLength;
2383 BString url;
2384 uint8 type = CheckForURL(data + loop, urlLength, &url);
2386 if (type) {
2387 if (!Insert(line, count, false, isHeader))
2388 return false;
2389 count = 0;
2391 hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text));
2392 if (enclosure == NULL)
2393 return false;
2395 memset(enclosure, 0, sizeof(hyper_text));
2396 fView->GetSelection(&enclosure->text_start,
2397 &enclosure->text_end);
2398 enclosure->type = type;
2399 enclosure->name = strdup(url.String());
2400 if (enclosure->name == NULL) {
2401 free(enclosure);
2402 return false;
2405 Insert(&data[loop], urlLength, true, isHeader);
2406 enclosure->text_end += urlLength;
2407 loop += urlLength - 1;
2409 fEnclosures->AddItem(enclosure);
2410 continue;
2413 if (!fRaw && fMime && data[loop] == '=') {
2414 if ((loop) && (loop < data_len - 1) && (data[loop + 1] == '\r'))
2415 loop += 2;
2416 else
2417 line[count++] = data[loop];
2418 } else if (data[loop] != '\r')
2419 line[count++] = data[loop];
2421 if (count > 511 || (count && loop == data_len - 1)) {
2422 if (!Insert(line, count, false, isHeader))
2423 return false;
2424 count = 0;
2427 return true;
2431 bool
2432 TTextView::Reader::Insert(const char *line, int32 count, bool isHyperLink,
2433 bool isHeader)
2435 if (!count)
2436 return true;
2438 BFont font(fView->Font());
2439 TextRunArray style(count / 8 + 8);
2441 if (fView->fColoredQuotes && !isHeader && !isHyperLink) {
2442 FillInQuoteTextRuns(fView, &fQuoteContext, line, count, font,
2443 &style.Array(), style.MaxEntries());
2444 } else {
2445 text_run_array &array = style.Array();
2446 array.count = 1;
2447 array.runs[0].offset = 0;
2448 if (isHeader) {
2449 array.runs[0].color = isHyperLink ? kHyperLinkColor : kHeaderColor;
2450 font.SetSize(font.Size() * 0.9);
2451 } else {
2452 array.runs[0].color = isHyperLink
2453 ? kHyperLinkColor : kNormalTextColor;
2455 array.runs[0].font = font;
2458 if (!fView->Window()->Lock())
2459 return false;
2461 fView->Insert(fView->TextLength(), line, count, &style.Array());
2463 fView->Window()->Unlock();
2464 return true;
2468 status_t
2469 TTextView::Reader::Run(void *_this)
2471 Reader *reader = (Reader *)_this;
2472 TTextView *view = reader->fView;
2473 char *msg = NULL;
2474 off_t size = 0;
2475 int32 len = 0;
2477 if (!reader->Lock())
2478 return B_INTERRUPTED;
2480 BFile *file = dynamic_cast<BFile *>(reader->fMail->Data());
2481 if (file != NULL) {
2482 len = header_len(file);
2484 if (reader->fHeader)
2485 size = len;
2486 if (reader->fRaw || !reader->fMime)
2487 file->GetSize(&size);
2489 if (size != 0 && (msg = (char *)malloc(size)) == NULL)
2490 goto done;
2491 file->Seek(0, 0);
2493 if (msg)
2494 size = file->Read(msg, size);
2497 // show the header?
2498 if (reader->fHeader && len) {
2499 // strip all headers except "From", "To", "Reply-To", "Subject", and "Date"
2500 if (reader->fStripHeader) {
2501 const char *header = msg;
2502 char *buffer = NULL;
2504 while (strncmp(header, "\r\n", 2)) {
2505 const char *eol = header;
2506 while ((eol = strstr(eol, "\r\n")) != NULL && isspace(eol[2]))
2507 eol += 2;
2508 if (eol == NULL)
2509 break;
2511 eol += 2; // CR+LF belong to the line
2512 size_t length = eol - header;
2514 buffer = (char *)realloc(buffer, length + 1);
2515 if (buffer == NULL)
2516 goto done;
2518 memcpy(buffer, header, length);
2520 length = rfc2047_to_utf8(&buffer, &length, length);
2522 if (!strncasecmp(header, "Reply-To: ", 10)
2523 || !strncasecmp(header, "To: ", 4)
2524 || !strncasecmp(header, "From: ", 6)
2525 || !strncasecmp(header, "Subject: ", 8)
2526 || !strncasecmp(header, "Date: ", 6))
2527 reader->Process(buffer, length, true);
2529 header = eol;
2531 free(buffer);
2532 reader->Process("\r\n", 2, true);
2534 else if (!reader->Process(msg, len, true))
2535 goto done;
2538 if (reader->fRaw) {
2539 if (!reader->Process((const char *)msg + len, size - len))
2540 goto done;
2541 } else {
2542 //reader->fFile->Seek(0, 0);
2543 //BEmailMessage *mail = new BEmailMessage(reader->fFile);
2544 BEmailMessage *mail = reader->fMail;
2546 // at first, insert the mail body
2547 BTextMailComponent *body = NULL;
2548 if (mail->BodyText() && !view->fStopLoading) {
2549 char *bodyText = const_cast<char *>(mail->BodyText());
2550 int32 bodyLength = strlen(bodyText);
2551 body = mail->Body();
2552 bool isHTML = false;
2554 BMimeType type;
2555 if (body->MIMEType(&type) == B_OK && type == "text/html") {
2556 // strip out HTML tags
2557 char *t = bodyText, *a, *end = bodyText + bodyLength;
2558 bodyText = (char *)malloc(bodyLength + 1);
2559 isHTML = true;
2561 // TODO: is it correct to assume that the text is in Latin-1?
2562 // because if it isn't, the code below won't work correctly...
2564 for (a = bodyText; t < end; t++) {
2565 int32 c = *t;
2567 // compact spaces
2568 bool space = false;
2569 while (c && (c == ' ' || c == '\t')) {
2570 c = *(++t);
2571 space = true;
2573 if (space) {
2574 c = ' ';
2575 t--;
2576 } else if (FilterHTMLTag(c, &t, end)) // the tag filter
2577 continue;
2579 Unicode2UTF8(c, &a);
2582 *a = 0;
2583 bodyLength = strlen(bodyText);
2584 body = NULL; // to add the HTML text as enclosure
2586 if (!reader->Process(bodyText, bodyLength))
2587 goto done;
2589 if (isHTML)
2590 free(bodyText);
2593 if (!reader->ParseMail(mail, body))
2594 goto done;
2596 //reader->fView->fMail = mail;
2599 if (!view->fStopLoading && view->Window()->Lock()) {
2600 view->Select(0, 0);
2601 view->MakeSelectable(true);
2602 if (!reader->fIncoming)
2603 view->MakeEditable(true);
2605 view->Window()->Unlock();
2608 done:
2609 // restore the reading position if available
2610 view->Window()->PostMessage(M_READ_POS);
2612 reader->Unlock();
2614 delete reader;
2615 free(msg);
2617 return B_NO_ERROR;
2621 status_t
2622 TTextView::Reader::Unlock()
2624 return release_sem(fStopSem);
2628 bool
2629 TTextView::Reader::Lock()
2631 if (acquire_sem_etc(fStopSem, 1, B_TIMEOUT, 0) != B_NO_ERROR)
2632 return false;
2634 return true;
2638 //====================================================================
2639 // #pragma mark -
2642 TSavePanel::TSavePanel(hyper_text *enclosure, TTextView *view)
2643 : BFilePanel(B_SAVE_PANEL)
2645 fEnclosure = enclosure;
2646 fView = view;
2647 if (enclosure->name)
2648 SetSaveText(enclosure->name);
2652 void
2653 TSavePanel::SendMessage(const BMessenger * /* messenger */, BMessage *msg)
2655 const char *name = NULL;
2656 BMessage save(M_SAVE);
2657 entry_ref ref;
2659 if ((!msg->FindRef("directory", &ref)) && (!msg->FindString("name", &name))) {
2660 save.AddPointer("enclosure", fEnclosure);
2661 save.AddString("name", name);
2662 save.AddRef("directory", &ref);
2663 fView->Window()->PostMessage(&save, fView);
2668 void
2669 TSavePanel::SetEnclosure(hyper_text *enclosure)
2671 fEnclosure = enclosure;
2672 if (enclosure->name)
2673 SetSaveText(enclosure->name);
2674 else
2675 SetSaveText("");
2677 if (!IsShowing())
2678 Show();
2679 Window()->Activate();
2683 //--------------------------------------------------------------------
2684 // #pragma mark -
2687 void
2688 TTextView::InsertText(const char *insertText, int32 length, int32 offset,
2689 const text_run_array *runs)
2691 ContentChanged();
2693 // Undo function
2695 int32 cursorPos, dummy;
2696 GetSelection(&cursorPos, &dummy);
2698 if (fInputMethodUndoState.active) {
2699 // IMアクティブ時は、一旦別のバッファへ記憶
2700 fInputMethodUndoBuffer.AddUndo(insertText, length, offset, K_INSERTED, cursorPos);
2701 fInputMethodUndoState.replace = false;
2702 } else {
2703 if (fUndoState.replaced) {
2704 fUndoBuffer.AddUndo(insertText, length, offset, K_REPLACED, cursorPos);
2705 } else {
2706 if (length == 1 && insertText[0] == 0x0a)
2707 fUndoBuffer.MakeNewUndoItem();
2709 fUndoBuffer.AddUndo(insertText, length, offset, K_INSERTED, cursorPos);
2711 if (length == 1 && insertText[0] == 0x0a)
2712 fUndoBuffer.MakeNewUndoItem();
2716 fUndoState.replaced = false;
2717 fUndoState.deleted = false;
2719 struct text_runs : text_run_array { text_run _runs[1]; } style;
2720 if (runs == NULL && IsEditable()) {
2721 style.count = 1;
2722 style.runs[0].offset = 0;
2723 style.runs[0].font = fFont;
2724 style.runs[0].color = kNormalTextColor;
2725 runs = &style;
2728 BTextView::InsertText(insertText, length, offset, runs);
2730 if (fSpellCheck && IsEditable())
2732 UpdateSpellMarks(offset, length);
2734 rgb_color color;
2735 GetFontAndColor(offset - 1, NULL, &color);
2736 const char *text = Text();
2738 if (length > 1
2739 || isalpha(text[offset + 1])
2740 || (!isalpha(text[offset]) && text[offset] != '\'')
2741 || (color.red == kSpellTextColor.red
2742 && color.green == kSpellTextColor.green
2743 && color.blue == kSpellTextColor.blue))
2745 int32 start, end;
2746 FindSpellBoundry(length, offset, &start, &end);
2748 DSPELL(printf("Offset %ld, start %ld, end %ld\n", offset, start, end));
2749 DSPELL(printf("\t\"%10.10s...\"\n", text + start));
2751 CheckSpelling(start, end);
2757 void
2758 TTextView::DeleteText(int32 start, int32 finish)
2760 ContentChanged();
2762 // Undo function
2763 int32 cursorPos, dummy;
2764 GetSelection(&cursorPos, &dummy);
2765 if (fInputMethodUndoState.active) {
2766 if (fInputMethodUndoState.replace) {
2767 fUndoBuffer.AddUndo(&Text()[start], finish - start, start, K_DELETED, cursorPos);
2768 fInputMethodUndoState.replace = false;
2769 } else {
2770 fInputMethodUndoBuffer.AddUndo(&Text()[start], finish - start, start,
2771 K_DELETED, cursorPos);
2773 } else
2774 fUndoBuffer.AddUndo(&Text()[start], finish - start, start, K_DELETED, cursorPos);
2776 fUndoState.deleted = true;
2777 fUndoState.replaced = true;
2779 BTextView::DeleteText(start, finish);
2780 if (fSpellCheck && IsEditable()) {
2781 UpdateSpellMarks(start, start - finish);
2783 int32 s, e;
2784 FindSpellBoundry(1, start, &s, &e);
2785 CheckSpelling(s, e);
2790 void
2791 TTextView::ContentChanged(void)
2793 BLooper *looper = Looper();
2794 if (looper == NULL)
2795 return;
2797 BMessage msg(FIELD_CHANGED);
2798 msg.AddInt32("bitmask", FIELD_BODY);
2799 msg.AddPointer("source", this);
2800 looper->PostMessage(&msg);
2804 void
2805 TTextView::CheckSpelling(int32 start, int32 end, int32 flags)
2807 const char *text = Text();
2808 const char *next, *endPtr, *word = NULL;
2809 int32 wordLength = 0, wordOffset;
2810 int32 nextHighlight = start;
2811 BString testWord;
2812 bool isCap = false;
2813 bool isAlpha;
2814 bool isApost;
2816 for (next = text + start, endPtr = text + end; next <= endPtr; next++) {
2817 //printf("next=%c\n", *next);
2818 // ToDo: this has to be refined to other languages...
2819 // Alpha signifies the start of a word
2820 isAlpha = isalpha(*next);
2821 isApost = (*next == '\'');
2822 if (!word && isAlpha) {
2823 //printf("Found word start\n");
2824 word = next;
2825 wordLength++;
2826 isCap = isupper(*word);
2827 } else if (word && (isAlpha || isApost) && !(isApost && !isalpha(next[1]))
2828 && !(isCap && isApost && (next[1] == 's'))) {
2829 // Word continues check
2830 wordLength++;
2831 //printf("Word continues...\n");
2832 } else if (word) {
2833 // End of word reached
2835 //printf("Word End\n");
2836 // Don't check single characters
2837 if (wordLength > 1) {
2838 bool isUpper = true;
2840 // Look for all uppercase
2841 for (int32 i = 0; i < wordLength; i++) {
2842 if (word[i] == '\'')
2843 break;
2845 if (islower(word[i])) {
2846 isUpper = false;
2847 break;
2851 // Don't check all uppercase words
2852 if (!isUpper) {
2853 bool foundMatch = false;
2854 wordOffset = word - text;
2855 testWord.SetTo(word, wordLength);
2857 testWord = testWord.ToLower();
2858 DSPELL(printf("Testing: \"%s\"\n", testWord.String()));
2860 int32 key = -1;
2861 if (gDictCount)
2862 key = gExactWords[0]->GetKey(testWord.String());
2864 // Search all dictionaries
2865 for (int32 i = 0; i < gDictCount; i++) {
2866 if (gExactWords[i]->Lookup(key) >= 0) {
2867 foundMatch = true;
2868 break;
2872 if (!foundMatch) {
2873 if (flags & S_CLEAR_ERRORS)
2874 RemoveSpellMark(nextHighlight, wordOffset);
2876 if (flags & S_SHOW_ERRORS)
2877 AddSpellMark(wordOffset, wordOffset + wordLength);
2878 } else if (flags & S_CLEAR_ERRORS)
2879 RemoveSpellMark(nextHighlight, wordOffset + wordLength);
2881 nextHighlight = wordOffset + wordLength;
2884 // Reset state to looking for word
2885 word = NULL;
2886 wordLength = 0;
2890 if (nextHighlight <= end
2891 && (flags & S_CLEAR_ERRORS) != 0
2892 && nextHighlight < TextLength())
2893 SetFontAndColor(nextHighlight, end, NULL, B_FONT_ALL, &kNormalTextColor);
2897 void
2898 TTextView::FindSpellBoundry(int32 length, int32 offset, int32 *_start, int32 *_end)
2900 int32 start, end, textLength;
2901 const char *text = Text();
2902 textLength = TextLength();
2904 for (start = offset - 1; start >= 0
2905 && (isalpha(text[start]) || text[start] == '\''); start--) {}
2907 start++;
2909 for (end = offset + length; end < textLength
2910 && (isalpha(text[end]) || text[end] == '\''); end++) {}
2912 *_start = start;
2913 *_end = end;
2917 TTextView::spell_mark *
2918 TTextView::FindSpellMark(int32 start, int32 end, spell_mark **_previousMark)
2920 spell_mark *lastMark = NULL;
2922 for (spell_mark *spellMark = fFirstSpellMark; spellMark; spellMark = spellMark->next) {
2923 if (spellMark->start < end && spellMark->end > start) {
2924 if (_previousMark)
2925 *_previousMark = lastMark;
2926 return spellMark;
2929 lastMark = spellMark;
2931 return NULL;
2935 void
2936 TTextView::UpdateSpellMarks(int32 offset, int32 length)
2938 DSPELL(printf("UpdateSpellMarks: offset = %ld, length = %ld\n", offset, length));
2940 spell_mark *spellMark;
2941 for (spellMark = fFirstSpellMark; spellMark; spellMark = spellMark->next) {
2942 DSPELL(printf("\tfound: %ld - %ld\n", spellMark->start, spellMark->end));
2944 if (spellMark->end < offset)
2945 continue;
2947 if (spellMark->start > offset)
2948 spellMark->start += length;
2950 spellMark->end += length;
2952 DSPELL(printf("\t-> reset: %ld - %ld\n", spellMark->start, spellMark->end));
2957 status_t
2958 TTextView::AddSpellMark(int32 start, int32 end)
2960 DSPELL(printf("AddSpellMark: start = %ld, end = %ld\n", start, end));
2962 // check if there is already a mark for this passage
2963 spell_mark *spellMark = FindSpellMark(start, end);
2964 if (spellMark) {
2965 if (spellMark->start == start && spellMark->end == end) {
2966 DSPELL(printf("\tfound one\n"));
2967 return B_OK;
2970 DSPELL(printf("\tremove old one\n"));
2971 RemoveSpellMark(start, end);
2974 spellMark = (spell_mark *)malloc(sizeof(spell_mark));
2975 if (spellMark == NULL)
2976 return B_NO_MEMORY;
2978 spellMark->start = start;
2979 spellMark->end = end;
2980 spellMark->style = RunArray(start, end);
2982 // set the spell marks appearance
2983 BFont font(fFont);
2984 font.SetFace(B_BOLD_FACE | B_ITALIC_FACE);
2985 SetFontAndColor(start, end, &font, B_FONT_ALL, &kSpellTextColor);
2987 // add it to the queue
2988 spellMark->next = fFirstSpellMark;
2989 fFirstSpellMark = spellMark;
2991 return B_OK;
2995 bool
2996 TTextView::RemoveSpellMark(int32 start, int32 end)
2998 DSPELL(printf("RemoveSpellMark: start = %ld, end = %ld\n", start, end));
3000 // find spell mark
3001 spell_mark *lastMark = NULL;
3002 spell_mark *spellMark = FindSpellMark(start, end, &lastMark);
3003 if (spellMark == NULL) {
3004 DSPELL(printf("\tnot found!\n"));
3005 return false;
3008 DSPELL(printf("\tfound: %ld - %ld\n", spellMark->start, spellMark->end));
3010 // dequeue the spell mark
3011 if (lastMark)
3012 lastMark->next = spellMark->next;
3013 else
3014 fFirstSpellMark = spellMark->next;
3016 if (spellMark->start < start)
3017 start = spellMark->start;
3018 if (spellMark->end > end)
3019 end = spellMark->end;
3021 // reset old text run array
3022 SetRunArray(start, end, spellMark->style);
3024 free(spellMark->style);
3025 free(spellMark);
3027 return true;
3031 void
3032 TTextView::RemoveSpellMarks()
3034 spell_mark *spellMark, *nextMark;
3036 for (spellMark = fFirstSpellMark; spellMark; spellMark = nextMark) {
3037 nextMark = spellMark->next;
3039 // reset old text run array
3040 SetRunArray(spellMark->start, spellMark->end, spellMark->style);
3042 free(spellMark->style);
3043 free(spellMark);
3046 fFirstSpellMark = NULL;
3050 void
3051 TTextView::EnableSpellCheck(bool enable)
3053 if (fSpellCheck == enable)
3054 return;
3056 fSpellCheck = enable;
3057 int32 textLength = TextLength();
3058 if (fSpellCheck) {
3059 // work-around for a bug in the BTextView class
3060 // which causes lots of flicker
3061 int32 start, end;
3062 GetSelection(&start, &end);
3063 if (start != end)
3064 Select(start, start);
3066 CheckSpelling(0, textLength);
3068 if (start != end)
3069 Select(start, end);
3071 else
3072 RemoveSpellMarks();
3076 void
3077 TTextView::WindowActivated(bool flag)
3079 if (!flag) {
3080 // WindowActivated(false) は、IM も Inactive になり、そのまま確定される。
3081 // しかしこの場合、input_server が B_INPUT_METHOD_EVENT(B_INPUT_METHOD_STOPPED)
3082 // を送ってこないまま矛盾してしまうので、やむを得ずここでつじつまあわせ処理している。
3083 // OpenBeOSで修正されることを願って暫定処置としている。
3084 fInputMethodUndoState.active = false;
3085 // fInputMethodUndoBufferに溜まっている最後のデータがK_INSERTEDなら(確定)正規のバッファへ追加
3086 if (fInputMethodUndoBuffer.CountItems() > 0) {
3087 KUndoItem *item = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1);
3088 if (item->History == K_INSERTED) {
3089 fUndoBuffer.MakeNewUndoItem();
3090 fUndoBuffer.AddUndo(item->RedoText, item->Length, item->Offset,
3091 item->History, item->CursorPos);
3092 fUndoBuffer.MakeNewUndoItem();
3094 fInputMethodUndoBuffer.MakeEmpty();
3097 BTextView::WindowActivated(flag);
3101 void
3102 TTextView::AddQuote(int32 start, int32 finish)
3104 BRect rect = Bounds();
3106 int32 lineStart;
3107 GoToLine(CurrentLine());
3108 GetSelection(&lineStart, &lineStart);
3109 lineStart = LineStart(lineStart);
3111 // make sure that we're changing the whole last line, too
3112 int32 lineEnd = finish > lineStart ? finish - 1 : finish;
3114 const char *text = Text();
3115 while (text[lineEnd] && text[lineEnd] != '\n')
3116 lineEnd++;
3118 Select(lineStart, lineEnd);
3120 int32 textLength = lineEnd - lineStart;
3121 char *text = (char *)malloc(textLength + 1);
3122 if (text == NULL)
3123 return;
3125 GetText(lineStart, textLength, text);
3127 int32 quoteLength = strlen(QUOTE);
3128 int32 targetLength = 0;
3129 char *target = NULL;
3130 int32 lastLine = 0;
3132 for (int32 index = 0; index < textLength; index++) {
3133 if (text[index] == '\n' || index == textLength - 1) {
3134 // add quote to this line
3135 int32 lineLength = index - lastLine + 1;
3137 target = (char *)realloc(target, targetLength + lineLength + quoteLength);
3138 if (target == NULL) {
3139 // free the old buffer?
3140 free(text);
3141 return;
3144 // copy the quote sign
3145 memcpy(&target[targetLength], QUOTE, quoteLength);
3146 targetLength += quoteLength;
3148 // copy the rest of the line
3149 memcpy(&target[targetLength], &text[lastLine], lineLength);
3150 targetLength += lineLength;
3152 lastLine = index + 1;
3156 // replace with quoted text
3157 free(text);
3158 Delete();
3160 if (fColoredQuotes) {
3161 const BFont *font = Font();
3162 TextRunArray style(targetLength / 8 + 8);
3164 FillInQuoteTextRuns(NULL, NULL, target, targetLength, font,
3165 &style.Array(), style.MaxEntries());
3166 Insert(target, targetLength, &style.Array());
3167 } else
3168 Insert(target, targetLength);
3170 free(target);
3172 // redo the old selection (compute the new start if necessary)
3173 Select(start + quoteLength, finish + (targetLength - textLength));
3175 ScrollTo(rect.LeftTop());
3179 void
3180 TTextView::RemoveQuote(int32 start, int32 finish)
3182 BRect rect = Bounds();
3184 GoToLine(CurrentLine());
3185 int32 lineStart;
3186 GetSelection(&lineStart, &lineStart);
3187 lineStart = LineStart(lineStart);
3189 // make sure that we're changing the whole last line, too
3190 int32 lineEnd = finish > lineStart ? finish - 1 : finish;
3191 const char *text = Text();
3192 while (text[lineEnd] && text[lineEnd] != '\n')
3193 lineEnd++;
3195 Select(lineStart, lineEnd);
3197 int32 length = lineEnd - lineStart;
3198 char *target = (char *)malloc(length + 1);
3199 if (target == NULL)
3200 return;
3202 int32 quoteLength = strlen(QUOTE);
3203 int32 removed = 0;
3204 text += lineStart;
3206 for (int32 index = 0; index < length;) {
3207 // find out the length of the current line
3208 int32 lineLength = 0;
3209 while (index + lineLength < length && text[lineLength] != '\n')
3210 lineLength++;
3212 // include the newline to be part of this line
3213 if (text[lineLength] == '\n' && index + lineLength + 1 < length)
3214 lineLength++;
3216 if (!strncmp(text, QUOTE, quoteLength)) {
3217 // remove quote
3218 length -= quoteLength;
3219 removed += quoteLength;
3221 lineLength -= quoteLength;
3222 text += quoteLength;
3225 if (lineLength == 0) {
3226 target[index] = '\0';
3227 break;
3230 memcpy(&target[index], text, lineLength);
3232 text += lineLength;
3233 index += lineLength;
3236 if (removed) {
3237 Delete();
3239 if (fColoredQuotes) {
3240 const BFont *font = Font();
3241 TextRunArray style(length / 8 + 8);
3243 FillInQuoteTextRuns(NULL, NULL, target, length, font,
3244 &style.Array(), style.MaxEntries());
3245 Insert(target, length, &style.Array());
3246 } else
3247 Insert(target, length);
3249 // redo old selection
3250 bool noSelection = start == finish;
3252 if (start > lineStart + quoteLength)
3253 start -= quoteLength;
3254 else
3255 start = lineStart;
3257 if (noSelection)
3258 finish = start;
3259 else
3260 finish -= removed;
3263 free(target);
3265 Select(start, finish);
3266 ScrollTo(rect.LeftTop());
3270 int32
3271 TTextView::LineStart(int32 offset)
3273 if (offset <= 0)
3274 return 0;
3276 while (offset > 0) {
3277 offset = PreviousByte(offset);
3278 if (ByteAt(offset) == B_ENTER)
3279 return offset + 1;
3282 return offset;
3286 int32
3287 TTextView::PreviousByte(int32 offset) const
3289 if (offset <= 0)
3290 return 0;
3292 int32 count = 6;
3294 for (--offset; offset > 0 && count; --offset, --count) {
3295 if ((ByteAt(offset) & 0xC0) != 0x80)
3296 break;
3299 return count ? offset : 0;
3303 void
3304 TTextView::Undo(BClipboard */*clipboard*/)
3306 if (fInputMethodUndoState.active)
3307 return;
3309 int32 length, offset, cursorPos;
3310 undo_type history;
3311 char *text;
3312 status_t status;
3314 status = fUndoBuffer.Undo(&text, &length, &offset, &history, &cursorPos);
3315 if (status == B_OK) {
3316 fUndoBuffer.Off();
3318 switch (history) {
3319 case K_INSERTED:
3320 BTextView::Delete(offset, offset + length);
3321 Select(offset, offset);
3322 break;
3324 case K_DELETED:
3325 BTextView::Insert(offset, text, length);
3326 Select(offset, offset + length);
3327 break;
3329 case K_REPLACED:
3330 BTextView::Delete(offset, offset + length);
3331 status = fUndoBuffer.Undo(&text, &length, &offset, &history, &cursorPos);
3332 if (status == B_OK && history == K_DELETED) {
3333 BTextView::Insert(offset, text, length);
3334 Select(offset, offset + length);
3335 } else {
3336 ::beep();
3337 BAlert* alert = new BAlert("",
3338 B_TRANSLATE("Inconsistency occurred in the undo/redo "
3339 "buffer."), B_TRANSLATE("OK"));
3340 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3341 alert->Go();
3343 break;
3345 ScrollToSelection();
3346 ContentChanged();
3347 fUndoBuffer.On();
3352 void
3353 TTextView::Redo()
3355 if (fInputMethodUndoState.active)
3356 return;
3358 int32 length, offset, cursorPos;
3359 undo_type history;
3360 char *text;
3361 status_t status;
3362 bool replaced;
3364 status = fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced);
3365 if (status == B_OK) {
3366 fUndoBuffer.Off();
3368 switch (history) {
3369 case K_INSERTED:
3370 BTextView::Insert(offset, text, length);
3371 Select(offset, offset + length);
3372 break;
3374 case K_DELETED:
3375 BTextView::Delete(offset, offset + length);
3376 if (replaced) {
3377 fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced);
3378 BTextView::Insert(offset, text, length);
3380 Select(offset, offset + length);
3381 break;
3383 case K_REPLACED:
3384 ::beep();
3385 BAlert* alert = new BAlert("",
3386 B_TRANSLATE("Inconsistency occurred in the undo/redo "
3387 "buffer."), B_TRANSLATE("OK"));
3388 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3389 alert->Go();
3390 break;
3392 ScrollToSelection();
3393 ContentChanged();
3394 fUndoBuffer.On();