Bug Summary

File:/boot/home/haiku/haiku/src/apps/mail/Content.cpp
Location:line 3154, column 5
Description:Potential leak of memory pointed to by 'target'

Annotated Source Code

1/*
2Open Tracker License
3
4Terms and Conditions
5
6Copyright (c) 1991-2001, Be Incorporated. All rights reserved.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of
9this software and associated documentation files (the "Software"), to deal in
10the Software without restriction, including without limitation the rights to
11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12of the Software, and to permit persons to whom the Software is furnished to do
13so, subject to the following conditions:
14
15The above copyright notice and this permission notice applies to all licensees
16and shall be included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN
23CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of Be Incorporated shall not be
26used in advertising or otherwise to promote the sale, use or other dealings in
27this Software without prior written authorization from Be Incorporated.
28
29BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or
30registered trademarks of Be Incorporated in the United States and other
31countries. Other brand product names are registered trademarks or trademarks
32of their respective holders. All rights reserved.
33*/
34
35
36#include <stdlib.h>
37#include <string.h>
38#include <stdio.h>
39#include <ctype.h>
40
41#include <Alert.h>
42#include <Beep.h>
43#include <Clipboard.h>
44#include <Debug.h>
45#include <E-mail.h>
46#include <Input.h>
47#include <Locale.h>
48#include <MenuItem.h>
49#include <Mime.h>
50#include <NodeInfo.h>
51#include <NodeMonitor.h>
52#include <Path.h>
53#include <PopUpMenu.h>
54#include <Region.h>
55#include <Roster.h>
56#include <ScrollView.h>
57#include <TextView.h>
58#include <UTF8.h>
59
60#include <MailMessage.h>
61#include <MailAttachment.h>
62#include <mail_util.h>
63
64#include "MailApp.h"
65#include "MailSupport.h"
66#include "MailWindow.h"
67#include "Messages.h"
68#include "Content.h"
69#include "Utilities.h"
70#include "FieldMsg.h"
71#include "Words.h"
72
73
74#define DEBUG_SPELLCHECK0 0
75#if DEBUG_SPELLCHECK0
76# define DSPELL(x); x
77#else
78# define DSPELL(x); ;
79#endif
80
81
82#define B_TRANSLATION_CONTEXT"Mail" "Mail"
83
84
85const rgb_color kNormalTextColor = {0, 0, 0, 255};
86const rgb_color kSpellTextColor = {255, 0, 0, 255};
87const rgb_color kHyperLinkColor = {0, 0, 255, 255};
88const rgb_color kHeaderColor = {72, 72, 72, 255};
89
90const rgb_color kQuoteColors[] = {
91 {0, 0, 0x80, 0}, // 3rd, 6th, ... quote level color (blue)
92 {0, 0x80, 0, 0}, // 1st, 4th, ... quote level color (green)
93 {0x80, 0, 0, 0} // 2nd, ... (red)
94};
95const int32 kNumQuoteColors = 3;
96
97const rgb_color kDiffColors[] = {
98 {0xb0, 0, 0, 0}, // '-', red
99 {0, 0x90, 0, 0}, // '+', green
100 {0x6a, 0x6a, 0x6a, 0} // '@@', dark grey
101};
102
103void Unicode2UTF8(int32 c, char **out);
104
105
106inline bool
107IsInitialUTF8Byte(uchar b)
108{
109 return ((b & 0xC0) != 0x80);
110}
111
112
113void
114Unicode2UTF8(int32 c, char **out)
115{
116 char *s = *out;
117
118 ASSERT(c < 0x200000)(void)0;
119
120 if (c < 0x80)
121 *(s++) = c;
122 else if (c < 0x800) {
123 *(s++) = 0xc0 | (c >> 6);
124 *(s++) = 0x80 | (c & 0x3f);
125 } else if (c < 0x10000) {
126 *(s++) = 0xe0 | (c >> 12);
127 *(s++) = 0x80 | ((c >> 6) & 0x3f);
128 *(s++) = 0x80 | (c & 0x3f);
129 } else if (c < 0x200000) {
130 *(s++) = 0xf0 | (c >> 18);
131 *(s++) = 0x80 | ((c >> 12) & 0x3f);
132 *(s++) = 0x80 | ((c >> 6) & 0x3f);
133 *(s++) = 0x80 | (c & 0x3f);
134 }
135 *out = s;
136}
137
138
139static bool
140FilterHTMLTag(int32 &first, char **t, char *end)
141{
142 const char *newlineTags[] = {
143 "br", "/p", "/div", "/table", "/tr",
144 NULL__null};
145
146 char *a = *t;
147
148 // check for some common entities (in ISO-Latin-1)
149 if (first == '&') {
150 // filter out and convert decimal values
151 if (a[1] == '#' && sscanf(a + 2, "%" B_SCNd32"l" "d" ";", &first) == 1) {
152 t[0] += strchr(a, ';') - a;
153 return false;
154 }
155
156 const struct { const char *name; int32 code; } entities[] = {
157 // this list is sorted alphabetically to be binary searchable
158 // the current implementation doesn't do this, though
159
160 // "name" is the entity name,
161 // "code" is the corresponding unicode
162 {"AElig;", 0x00c6},
163 {"Aacute;", 0x00c1},
164 {"Acirc;", 0x00c2},
165 {"Agrave;", 0x00c0},
166 {"Aring;", 0x00c5},
167 {"Atilde;", 0x00c3},
168 {"Auml;", 0x00c4},
169 {"Ccedil;", 0x00c7},
170 {"Eacute;", 0x00c9},
171 {"Ecirc;", 0x00ca},
172 {"Egrave;", 0x00c8},
173 {"Euml;", 0x00cb},
174 {"Iacute;", 0x00cd},
175 {"Icirc;", 0x00ce},
176 {"Igrave;", 0x00cc},
177 {"Iuml;", 0x00cf},
178 {"Ntilde;", 0x00d1},
179 {"Oacute;", 0x00d3},
180 {"Ocirc;", 0x00d4},
181 {"Ograve;", 0x00d2},
182 {"Ouml;", 0x00d6},
183 {"Uacute;", 0x00da},
184 {"Ucirc;", 0x00db},
185 {"Ugrave;", 0x00d9},
186 {"Uuml;", 0x00dc},
187 {"aacute;", 0x00e1},
188 {"acirc;", 0x00e2},
189 {"aelig;", 0x00e6},
190 {"agrave;", 0x00e0},
191 {"amp;", '&'},
192 {"aring;", 0x00e5},
193 {"atilde;", 0x00e3},
194 {"auml;", 0x00e4},
195 {"ccedil;", 0x00e7},
196 {"copy;", 0x00a9},
197 {"eacute;", 0x00e9},
198 {"ecirc;", 0x00ea},
199 {"egrave;", 0x00e8},
200 {"euml;", 0x00eb},
201 {"gt;", '>'},
202 {"iacute;", 0x00ed},
203 {"icirc;", 0x00ee},
204 {"igrave;", 0x00ec},
205 {"iuml;", 0x00ef},
206 {"lt;", '<'},
207 {"nbsp;", ' '},
208 {"ntilde;", 0x00f1},
209 {"oacute;", 0x00f3},
210 {"ocirc;", 0x00f4},
211 {"ograve;", 0x00f2},
212 {"ouml;", 0x00f6},
213 {"quot;", '"'},
214 {"szlig;", 0x00f6},
215 {"uacute;", 0x00fa},
216 {"ucirc;", 0x00fb},
217 {"ugrave;", 0x00f9},
218 {"uuml;", 0x00fc},
219 {NULL__null, 0}
220 };
221
222 for (int32 i = 0; entities[i].name; i++) {
223 // entities are case-sensitive
224 int32 length = strlen(entities[i].name);
225 if (!strncmp(a + 1, entities[i].name, length)) {
226 t[0] += length; // note that the '&' is included here
227 first = entities[i].code;
228 return false;
229 }
230 }
231 }
232
233 // no tag to filter
234 if (first != '<')
235 return false;
236
237 a++;
238
239 // is the tag one of the newline tags?
240
241 bool newline = false;
242 for (int i = 0; newlineTags[i]; i++) {
243 int length = strlen(newlineTags[i]);
244 if (!strncasecmp(a, (char *)newlineTags[i], length) && !isalnum(a[length])(__ctype_b[(int)((a[length]))] & (unsigned short int)_ISalnum
)
) {
245 newline = true;
246 break;
247 }
248 }
249
250 // oh, it's not, so skip it!
251
252 if (!strncasecmp(a, "head", 4)) { // skip "head" completely
253 for (; a[0] && a < end; a++) {
254 // Find the end of the HEAD section, or the start of the BODY,
255 // which happens for some malformed spam.
256 if (strncasecmp (a, "</head", 6) == 0 ||
257 strncasecmp (a, "<body", 5) == 0)
258 break;
259 }
260 }
261
262 // skip until tag end
263 while (a[0] && a[0] != '>' && a < end)
264 a++;
265
266 t[0] = a;
267
268 if (newline) {
269 first = '\n';
270 return false;
271 }
272
273 return true;
274}
275
276
277/** Returns the type and length of the URL in the string if it is one.
278 * If the "url" string is specified, it will fill it with the complete
279 * URL that might differ from the one in the text itself (i.e. because
280 * of an prepended "http://").
281 */
282
283static uint8
284CheckForURL(const char *string, size_t &urlLength, BString *url = NULL__null)
285{
286 const char *urlPrefixes[] = {
287 "http://",
288 "ftp://",
289 "shttp://",
290 "https://",
291 "finger://",
292 "telnet://",
293 "gopher://",
294 "news://",
295 "nntp://",
296 "file://",
297 NULL__null
298 };
299
300 //
301 // Search for URL prefix
302 //
303 uint8 type = 0;
304 for (const char **prefix = urlPrefixes; *prefix != 0; prefix++) {
305 if (!cistrncmp(string, *prefix, strlen(*prefix))) {
306 type = TYPE_URL;
307 break;
308 }
309 }
310
311 //
312 // Not a URL? check for "mailto:" or "www."
313 //
314 if (type == 0 && !cistrncmp(string, "mailto:", 7))
315 type = TYPE_MAILTO;
316 if (type == 0 && !strncmp(string, "www.", 4)) {
317 // this type will be special cased later (and a http:// is added
318 // for the enclosure address)
319 type = TYPE_URL;
320 }
321 if (type == 0) {
322 // check for valid eMail addresses
323 const char *at = strchr(string, '@');
324 if (at) {
325 const char *pos = string;
326 bool readName = false;
327 for (; pos < at; pos++) {
328 // ToDo: are these all allowed characters?
329 if (!isalnum(pos[0])(__ctype_b[(int)((pos[0]))] & (unsigned short int)_ISalnum
)
&& pos[0] != '_' && pos[0] != '.' && pos[0] != '-')
330 break;
331
332 readName = true;
333 }
334 if (pos == at && readName)
335 type = TYPE_MAILTO;
336 }
337 }
338
339 if (type == 0)
340 return 0;
341
342 int32 index = strcspn(string, " \t<>)\"\\,\r\n");
343
344 // filter out some punctuation marks if they are the last character
345 char suffix = string[index - 1];
346 if (suffix == '.'
347 || suffix == ','
348 || suffix == '?'
349 || suffix == '!'
350 || suffix == ':'
351 || suffix == ';')
352 index--;
353
354 if (type == TYPE_URL) {
355 char *parenthesis = NULL__null;
356
357 // filter out a trailing ')' if there is no left parenthesis before
358 if (string[index - 1] == ')') {
359 char *parenthesis = strchr(string, '(');
360 if (parenthesis == NULL__null || parenthesis > string + index)
361 index--;
362 }
363
364 // filter out a trailing ']' if there is no left bracket before
365 if (parenthesis == NULL__null && string[index - 1] == ']') {
366 char *parenthesis = strchr(string, '[');
367 if (parenthesis == NULL__null || parenthesis > string + index)
368 index--;
369 }
370 }
371
372 if (url != NULL__null) {
373 // copy the address to the specified string
374 if (type == TYPE_URL && string[0] == 'w') {
375 // URL starts with "www.", so add the protocol to it
376 url->SetTo("http://");
377 url->Append(string, index);
378 } else if (type == TYPE_MAILTO && cistrncmp(string, "mailto:", 7)) {
379 // eMail address had no "mailto:" prefix
380 url->SetTo("mailto:");
381 url->Append(string, index);
382 } else
383 url->SetTo(string, index);
384 }
385 urlLength = index;
386
387 return type;
388}
389
390
391static void
392CopyQuotes(const char *text, size_t length, char *outText, size_t &outLength)
393{
394 // count qoute level (to be able to wrap quotes correctly)
395
396 const char *quote = QUOTE"> ";
397 int32 level = 0;
398 for (size_t i = 0; i < length; i++) {
399 if (text[i] == quote[0])
400 level++;
401 else if (text[i] != ' ' && text[i] != '\t')
402 break;
403 }
404
405 // if there are too much quotes, try to preserve the quote color level
406 if (level > 10)
407 level = kNumQuoteColors * 3 + (level % kNumQuoteColors);
408
409 // copy the quotes to outText
410
411 const int32 quoteLength = strlen(QUOTE"> ");
412 outLength = 0;
413 while (level-- > 0) {
414 strcpy(outText + outLength, QUOTE"> ");
415 outLength += quoteLength;
416 }
417}
418
419
420int32
421diff_mode(char c)
422{
423 if (c == '+')
424 return 2;
425 if (c == '-')
426 return 1;
427 if (c == '@')
428 return 3;
429 if (c == ' ')
430 return 0;
431
432 // everything else ends the diff mode
433 return -1;
434}
435
436
437bool
438is_quote_char(char c)
439{
440 return c == '>' || c == '|';
441}
442
443
444/*! Fills the specified text_run_array with the correct values for the
445 specified text.
446 If "view" is NULL, it will assume that "line" lies on a line break,
447 if not, it will correctly retrieve the number of quotes the current
448 line already has.
449*/
450void
451FillInQuoteTextRuns(BTextView* view, quote_context* context, const char* line,
452 int32 length, const BFont& font, text_run_array* style, int32 maxStyles)
453{
454 text_run* runs = style->runs;
455 int32 index = style->count;
456 bool begin;
457 int32 pos = 0;
458 int32 diffMode = 0;
459 bool inDiff = false;
460 bool wasDiff = false;
461 int32 level = 0;
462
463 // get index to the beginning of the current line
464
465 if (context != NULL__null) {
466 level = context->level;
467 diffMode = context->diff_mode;
468 begin = context->begin;
469 inDiff = context->in_diff;
470 wasDiff = context->was_diff;
471 } else if (view != NULL__null) {
472 int32 start, end;
473 view->GetSelection(&end, &end);
474
475 begin = view->TextLength() == 0
476 || view->ByteAt(view->TextLength() - 1) == '\n';
477
478 // the following line works only reliable when text wrapping is set to
479 // off; so the complicated version actually used here is necessary:
480 // start = view->OffsetAt(view->CurrentLine());
481
482 const char *text = view->Text();
483
484 if (!begin) {
485 // if the text is not the start of a new line, go back
486 // to the first character in the current line
487 for (start = end; start > 0; start--) {
488 if (text[start - 1] == '\n')
489 break;
490 }
491 }
492
493 // get number of nested qoutes for current line
494
495 if (!begin && start < end) {
496 begin = true;
497 // if there was no text in this line, there may come
498 // more nested quotes
499
500 diffMode = diff_mode(text[start]);
501 if (diffMode == 0) {
502 for (int32 i = start; i < end; i++) {
503 if (is_quote_char(text[i]))
504 level++;
505 else if (text[i] != ' ' && text[i] != '\t') {
506 begin = false;
507 break;
508 }
509 }
510 } else
511 inDiff = true;
512
513 if (begin) {
514 // skip leading spaces (tabs & newlines aren't allowed here)
515 while (line[pos] == ' ')
516 pos++;
517 }
518 }
519 } else
520 begin = true;
521
522 // set styles for all qoute levels in the text to be inserted
523
524 for (int32 pos = 0; pos < length;) {
525 int32 next;
526 if (begin && is_quote_char(line[pos])) {
527 begin = false;
528
529 while (pos < length && line[pos] != '\n') {
530 // insert style for each quote level
531 level++;
532
533 bool search = true;
534 for (next = pos + 1; next < length; next++) {
535 if ((search && is_quote_char(line[next]))
536 || line[next] == '\n')
537 break;
538 else if (search && line[next] != ' ' && line[next] != '\t')
539 search = false;
540 }
541
542 runs[index].offset = pos;
543 runs[index].font = font;
544 runs[index].color = level > 0
545 ? kQuoteColors[level % kNumQuoteColors] : kNormalTextColor;
546
547 pos = next;
548 if (++index >= maxStyles)
549 break;
550 }
551 } else {
552 if (begin) {
553 if (!inDiff) {
554 inDiff = !strncmp(&line[pos], "--- ", 4);
555 wasDiff = false;
556 }
557 if (inDiff) {
558 diffMode = diff_mode(line[pos]);
559 if (diffMode < 0) {
560 inDiff = false;
561 wasDiff = true;
562 }
563 }
564 }
565
566 runs[index].offset = pos;
567 runs[index].font = font;
568 if (wasDiff)
569 runs[index].color = kDiffColors[diff_mode('@') - 1];
570 else if (diffMode <= 0) {
571 runs[index].color = level > 0
572 ? kQuoteColors[level % kNumQuoteColors] : kNormalTextColor;
573 } else
574 runs[index].color = kDiffColors[diffMode - 1];
575
576 begin = false;
577
578 for (next = pos; next < length; next++) {
579 if (line[next] == '\n') {
580 begin = true;
581 wasDiff = false;
582 break;
583 }
584 }
585
586 pos = next;
587 index++;
588 }
589
590 if (pos < length)
591 begin = line[pos] == '\n';
592
593 if (begin) {
594 pos++;
595 level = 0;
596 wasDiff = false;
597
598 // skip one leading space (tabs & newlines aren't allowed here)
599 if (!inDiff && pos < length && line[pos] == ' ')
600 pos++;
601 }
602
603 if (index >= maxStyles)
604 break;
605 }
606 style->count = index;
607
608 if (context) {
609 // update context for next run
610 context->level = level;
611 context->diff_mode = diffMode;
612 context->begin = begin;
613 context->in_diff = inDiff;
614 context->was_diff = wasDiff;
615 }
616}
617
618
619// #pragma mark -
620
621
622TextRunArray::TextRunArray(size_t entries)
623 :
624 fNumEntries(entries)
625{
626 fArray = (text_run_array *)malloc(sizeof(int32) + sizeof(text_run) * entries);
627 if (fArray != NULL__null)
628 fArray->count = 0;
629}
630
631
632TextRunArray::~TextRunArray()
633{
634 free(fArray);
635}
636
637
638//====================================================================
639// #pragma mark -
640
641
642TContentView::TContentView(BRect rect, bool incoming, BFont* font,
643 bool showHeader, bool coloredQuotes)
644 :
645 BView(rect, "m_content", B_FOLLOW_ALL_rule_(_VIEW_TOP_, _VIEW_LEFT_, _VIEW_BOTTOM_, _VIEW_RIGHT_),
646 B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
647
648 fFocus(false),
649 fIncoming(incoming)
650{
651 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
652 fOffset = 12;
653
654 BRect r(rect);
655 r.OffsetTo(0, 0);
656 r.right -= B_V_SCROLL_BAR_WIDTH14.0f;
657 r.bottom -= B_H_SCROLL_BAR_HEIGHT14.0f;
658 r.top += 4;
659 BRect text(r);
660 text.OffsetTo(0, 0);
661 text.InsetBy(5, 5);
662
663 fTextView = new TTextView(r, text, fIncoming, this, font, showHeader,
664 coloredQuotes);
665 BScrollView *scroll = new BScrollView("", fTextView, B_FOLLOW_ALL_rule_(_VIEW_TOP_, _VIEW_LEFT_, _VIEW_BOTTOM_, _VIEW_RIGHT_), 0, true, true);
666 AddChild(scroll);
667}
668
669
670void
671TContentView::MessageReceived(BMessage *msg)
672{
673 switch (msg->what) {
674 case CHANGE_FONT:
675 {
676 BFont *font;
677 msg->FindPointer("font", (void **)&font);
678 fTextView->UpdateFont(font);
679 fTextView->Invalidate(Bounds());
680 break;
681 }
682
683 case M_QUOTE:
684 {
685 int32 start, finish;
686 fTextView->GetSelection(&start, &finish);
687 fTextView->AddQuote(start, finish);
688 break;
689 }
690 case M_REMOVE_QUOTE:
691 {
692 int32 start, finish;
693 fTextView->GetSelection(&start, &finish);
694 fTextView->RemoveQuote(start, finish);
695 break;
696 }
697
698 case M_SIGNATURE:
699 {
700 if (fTextView->IsReaderThreadRunning()) {
701 // Do not add the signature until the reader thread
702 // is finished. Resubmit the message for later processing
703 Window()->PostMessage(msg);
704 break;
705 }
706
707 entry_ref ref;
708 msg->FindRef("ref", &ref);
709
710 BFile file(&ref, B_READ_ONLY0x0000);
711 if (file.InitCheck() == B_OK((int)0)) {
712 int32 start, finish;
713 fTextView->GetSelection(&start, &finish);
714
715 off_t size;
716 file.GetSize(&size);
717 if (size > 32768) // safety against corrupt signatures
718 break;
719
720 char *signature = (char *)malloc(size);
721 if (signature == NULL__null)
722 break;
723 ssize_t bytesRead = file.Read(signature, size);
724 if (bytesRead < B_OK((int)0)) {
725 free (signature);
726 break;
727 }
728
729 const char *text = fTextView->Text();
730 int32 length = fTextView->TextLength();
731
732 // reserve some empty lines before the signature
733 const char* newLines = "\n\n\n\n";
734 if (length && text[length - 1] == '\n')
735 newLines++;
736
737 fTextView->Select(length, length);
738 fTextView->Insert(newLines, strlen(newLines));
739 length += strlen(newLines);
740
741 // append the signature
742 fTextView->Select(length, length);
743 fTextView->Insert(signature, bytesRead);
744 fTextView->Select(length, length + bytesRead);
745 fTextView->ScrollToSelection();
746
747 // set the editing cursor position
748 fTextView->Select(length - 2 , length - 2);
749 fTextView->ScrollToSelection();
750 free (signature);
751 } else {
752 beep();
753 BAlert* alert = new BAlert("",
754 B_TRANSLATE("An error occurred trying to open this "BLocaleRoster::Default()->GetCatalog()->GetString(("An error occurred trying to open this "
"signature."), "Mail")
755 "signature.")BLocaleRoster::Default()->GetCatalog()->GetString(("An error occurred trying to open this "
"signature."), "Mail")
, B_TRANSLATE("Sorry")BLocaleRoster::Default()->GetCatalog()->GetString(("Sorry"
), "Mail")
);
756 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
757 alert->Go();
758 }
759 break;
760 }
761
762 case M_FIND:
763 FindString(msg->FindString("findthis"));
764 break;
765
766 default:
767 BView::MessageReceived(msg);
768 }
769}
770
771
772void
773TContentView::FindString(const char *str)
774{
775 int32 finish;
776 int32 pass = 0;
777 int32 start = 0;
778
779 if (str == NULL__null)
780 return;
781
782 //
783 // Start from current selection or from the beginning of the pool
784 //
785 const char *text = fTextView->Text();
786 int32 count = fTextView->TextLength();
787 fTextView->GetSelection(&start, &finish);
788 if (start != finish)
789 start = finish;
790 if (!count || text == NULL__null)
791 return;
792
793 //
794 // Do the find
795 //
796 while (pass < 2) {
797 long found = -1;
798 char lc = tolower(str[0])((int)__ctype_tolower[(int)(str[0])]);
799 char uc = toupper(str[0])((int)__ctype_toupper[(int)(str[0])]);
800 for (long i = start; i < count; i++) {
801 if (text[i] == lc || text[i] == uc) {
802 const char *s = str;
803 const char *t = text + i;
804 while (*s && (tolower(*s)((int)__ctype_tolower[(int)(*s)]) == tolower(*t)((int)__ctype_tolower[(int)(*t)]))) {
805 s++;
806 t++;
807 }
808 if (*s == 0) {
809 found = i;
810 break;
811 }
812 }
813 }
814
815 //
816 // Select the text if it worked
817 //
818 if (found != -1) {
819 Window()->Activate();
820 fTextView->Select(found, found + strlen(str));
821 fTextView->ScrollToSelection();
822 fTextView->MakeFocus(true);
823 return;
824 }
825 else if (start) {
826 start = 0;
827 text = fTextView->Text();
828 count = fTextView->TextLength();
829 pass++;
830 } else {
831 beep();
832 return;
833 }
834 }
835}
836
837
838void
839TContentView::Focus(bool focus)
840{
841 if (fFocus != focus) {
842 fFocus = focus;
843 Draw(Frame());
844 }
845}
846
847
848void
849TContentView::FrameResized(float /* width */, float /* height */)
850{
851 BRect r(fTextView->Bounds());
852 r.OffsetTo(0, 0);
853 r.InsetBy(5, 5);
854 fTextView->SetTextRect(r);
855}
856
857
858//====================================================================
859// #pragma mark -
860
861
862TTextView::TTextView(BRect frame, BRect text, bool incoming, TContentView *view,
863 BFont *font, bool showHeader, bool coloredQuotes)
864 :
865 BTextView(frame, "", text, B_FOLLOW_ALL_rule_(_VIEW_TOP_, _VIEW_LEFT_, _VIEW_BOTTOM_, _VIEW_RIGHT_), B_WILL_DRAW | B_NAVIGABLE),
866
867 fHeader(showHeader),
868 fColoredQuotes(coloredQuotes),
869 fReady(false),
870 fYankBuffer(NULL__null),
871 fLastPosition(-1),
872 fMail(NULL__null),
873 fFont(font),
874 fParent(view),
875 fStopLoading(false),
876 fThread(0),
877 fPanel(NULL__null),
878 fIncoming(incoming),
879 fSpellCheck(false),
880 fRaw(false),
881 fCursor(false),
882 fFirstSpellMark(NULL__null)
883{
884 fStopSem = create_sem(1, "reader_sem");
885 SetStylable(true);
886
887 fEnclosures = new BList();
888
889 // Enclosure pop up menu
890 fEnclosureMenu = new BPopUpMenu("Enclosure", false, false);
891 fEnclosureMenu->SetFont(be_plain_font);
892 fEnclosureMenu->AddItem(new BMenuItem(
893 B_TRANSLATE("Save attachment" B_UTF8_ELLIPSIS)BLocaleRoster::Default()->GetCatalog()->GetString(("Save attachment"
"\xE2\x80\xA6"), "Mail")
, new BMessage(M_SAVE)));
894 fEnclosureMenu->AddItem(new BMenuItem(B_TRANSLATE("Open attachment")BLocaleRoster::Default()->GetCatalog()->GetString(("Open attachment"
), "Mail")
,
895 new BMessage(M_OPEN)));
896
897 // Hyperlink pop up menu
898 fLinkMenu = new BPopUpMenu("Link", false, false);
899 fLinkMenu->SetFont(be_plain_font);
900 fLinkMenu->AddItem(new BMenuItem(B_TRANSLATE("Open this link")BLocaleRoster::Default()->GetCatalog()->GetString(("Open this link"
), "Mail")
,
901 new BMessage(M_OPEN)));
902 fLinkMenu->AddItem(new BMenuItem(B_TRANSLATE("Copy link location")BLocaleRoster::Default()->GetCatalog()->GetString(("Copy link location"
), "Mail")
,
903 new BMessage(M_COPY)));
904
905 SetDoesUndo(true);
906
907 //Undo function
908 fUndoBuffer.On();
909 fInputMethodUndoBuffer.On();
910 fUndoState.replaced = false;
911 fUndoState.deleted = false;
912 fInputMethodUndoState.active = false;
913 fInputMethodUndoState.replace = false;
914}
915
916
917TTextView::~TTextView()
918{
919 ClearList();
920 delete fPanel;
921
922 if (fYankBuffer)
923 free(fYankBuffer);
924
925 delete_sem(fStopSem);
926}
927
928
929void
930TTextView::UpdateFont(const BFont* newFont)
931{
932 fFont = *newFont;
933
934 // update the text run array safely with new font
935 text_run_array *runArray = RunArray(0, INT32_MAX(2147483647));
936 for (int i = 0; i < runArray->count; i++)
937 runArray->runs[i].font = *newFont;
938
939 SetRunArray(0, INT32_MAX(2147483647), runArray);
940 FreeRunArray(runArray);
941}
942
943
944void
945TTextView::AttachedToWindow()
946{
947 BTextView::AttachedToWindow();
948 fFont.SetSpacing(B_FIXED_SPACING);
949 SetFontAndColor(&fFont);
950
951 if (fMail != NULL__null) {
952 LoadMessage(fMail, false, NULL__null);
953 if (fIncoming)
954 MakeEditable(false);
955 }
956}
957
958
959void
960TTextView::KeyDown(const char *key, int32 count)
961{
962 char raw;
963 int32 end;
964 int32 start;
965 uint32 mods;
966 BMessage *msg;
967 int32 textLen = TextLength();
968
969 msg = Window()->CurrentMessage();
970 mods = msg->FindInt32("modifiers");
971
972 switch (key[0]) {
973 case B_HOME:
974 if (IsSelectable()) {
975 if (IsEditable())
976 BTextView::KeyDown(key, count);
977 else {
978 // scroll to the beginning
979 Select(0, 0);
980 ScrollToSelection();
981 }
982 }
983 break;
984
985 case B_END:
986 if (IsSelectable()) {
987 if (IsEditable())
988 BTextView::KeyDown(key, count);
989 else {
990 // scroll to the end
991 int32 length = TextLength();
992 Select(length, length);
993 ScrollToSelection();
994 }
995 }
996 break;
997
998 case 0x02: // ^b - back 1 char
999 if (IsSelectable()) {
1000 GetSelection(&start, &end);
1001 while (!IsInitialUTF8Byte(ByteAt(--start))) {
1002 if (start < 0) {
1003 start = 0;
1004 break;
1005 }
1006 }
1007 if (start >= 0) {
1008 Select(start, start);
1009 ScrollToSelection();
1010 }
1011 }
1012 break;
1013
1014 case B_DELETE:
1015 if (IsSelectable()) {
1016 if ((key[0] == B_DELETE) || (mods & B_CONTROL_KEY)) {
1017 // ^d
1018 if (IsEditable()) {
1019 GetSelection(&start, &end);
1020 if (start != end)
1021 Delete();
1022 else {
1023 for (end = start + 1; !IsInitialUTF8Byte(ByteAt(end)); end++) {
1024 if (end > textLen) {
1025 end = textLen;
1026 break;
1027 }
1028 }
1029 Select(start, end);
1030 Delete();
1031 }
1032 }
1033 }
1034 else
1035 Select(textLen, textLen);
1036 ScrollToSelection();
1037 }
1038 break;
1039
1040 case 0x05: // ^e - end of line
1041 if (IsSelectable() && (mods & B_CONTROL_KEY)) {
1042 if (CurrentLine() == CountLines() - 1)
1043 Select(TextLength(), TextLength());
1044 else {
1045 GoToLine(CurrentLine() + 1);
1046 GetSelection(&start, &end);
1047 Select(start - 1, start - 1);
1048 }
1049 }
1050 break;
1051
1052 case 0x06: // ^f - forward 1 char
1053 if (IsSelectable()) {
1054 GetSelection(&start, &end);
1055 if (end > start)
1056 start = end;
1057 else {
1058 for (end = start + 1; !IsInitialUTF8Byte(ByteAt(end));
1059 end++) {
1060 if (end > textLen) {
1061 end = textLen;
1062 break;
1063 }
1064 }
1065 start = end;
1066 }
1067 Select(start, start);
1068 ScrollToSelection();
1069 }
1070 break;
1071
1072 case 0x0e: // ^n - next line
1073 if (IsSelectable()) {
1074 raw = B_DOWN_ARROW;
1075 BTextView::KeyDown(&raw, 1);
1076 }
1077 break;
1078
1079 case 0x0f: // ^o - open line
1080 if (IsEditable()) {
1081 GetSelection(&start, &end);
1082 Delete();
1083
1084 char newLine = '\n';
1085 Insert(&newLine, 1);
1086 Select(start, start);
1087 ScrollToSelection();
1088 }
1089 break;
1090
1091 case B_PAGE_UP:
1092 if (mods & B_CONTROL_KEY) { // ^k kill text from cursor to e-o-line
1093 if (IsEditable()) {
1094 GetSelection(&start, &end);
1095 if ((start != fLastPosition) && (fYankBuffer)) {
1096 free(fYankBuffer);
1097 fYankBuffer = NULL__null;
1098 }
1099 fLastPosition = start;
1100 if (CurrentLine() < CountLines() - 1) {
1101 GoToLine(CurrentLine() + 1);
1102 GetSelection(&end, &end);
1103 end--;
1104 }
1105 else
1106 end = TextLength();
1107 if (end < start)
1108 break;
1109 if (start == end)
1110 end++;
1111 Select(start, end);
1112 if (fYankBuffer) {
1113 fYankBuffer = (char *)realloc(fYankBuffer,
1114 strlen(fYankBuffer) + (end - start) + 1);
1115 GetText(start, end - start,
1116 &fYankBuffer[strlen(fYankBuffer)]);
1117 } else {
1118 fYankBuffer = (char *)malloc(end - start + 1);
1119 GetText(start, end - start, fYankBuffer);
1120 }
1121 Delete();
1122 ScrollToSelection();
1123 }
1124 break;
1125 }
1126
1127 BTextView::KeyDown(key, count);
1128 break;
1129
1130 case 0x10: // ^p goto previous line
1131 if (IsSelectable()) {
1132 raw = B_UP_ARROW;
1133 BTextView::KeyDown(&raw, 1);
1134 }
1135 break;
1136
1137 case 0x19: // ^y yank text
1138 if (IsEditable() && fYankBuffer) {
1139 Delete();
1140 Insert(fYankBuffer);
1141 ScrollToSelection();
1142 }
1143 break;
1144
1145 default:
1146 BTextView::KeyDown(key, count);
1147 }
1148}
1149
1150
1151void
1152TTextView::MakeFocus(bool focus)
1153{
1154 if (!focus) {
1155 // ToDo: can someone please translate this? Otherwise I will remove it - axeld.
1156 // MakeFocus(false) は、IM も Inactive になり、そのまま確定される。
1157 // しかしこの場合、input_server が B_INPUT_METHOD_EVENT(B_INPUT_METHOD_STOPPED)
1158 // を送ってこないまま矛盾してしまうので、やむを得ずここでつじつまあわせ処理している。
1159 fInputMethodUndoState.active = false;
1160 // fInputMethodUndoBufferに溜まっている最後のデータがK_INSERTEDなら(確定)正規のバッファへ追加
1161 if (fInputMethodUndoBuffer.CountItems() > 0) {
1162 KUndoItem *item = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1);
1163 if (item->History == K_INSERTED) {
1164 fUndoBuffer.MakeNewUndoItem();
1165 fUndoBuffer.AddUndo(item->RedoText, item->Length, item->Offset, item->History, item->CursorPos);
1166 fUndoBuffer.MakeNewUndoItem();
1167 }
1168 fInputMethodUndoBuffer.MakeEmpty();
1169 }
1170 }
1171 BTextView::MakeFocus(focus);
1172
1173 fParent->Focus(focus);
1174}
1175
1176
1177void
1178TTextView::MessageReceived(BMessage *msg)
1179{
1180 switch (msg->what) {
1181 case B_SIMPLE_DATA:
1182 {
1183 if (fIncoming)
1184 break;
1185
1186 BMessage message(REFS_RECEIVED);
1187 bool isEnclosure = false;
1188 bool inserted = false;
1189
1190 off_t len = 0;
1191 int32 end;
1192 int32 start;
1193
1194 int32 index = 0;
1195 entry_ref ref;
1196 while (msg->FindRef("refs", index++, &ref) == B_OK((int)0)) {
1197 BFile file(&ref, B_READ_ONLY0x0000);
1198 if (file.InitCheck() == B_OK((int)0)) {
1199 BNodeInfo node(&file);
1200 char type[B_FILE_NAME_LENGTH(256)];
1201 node.GetType(type);
1202
1203 off_t size = 0;
1204 file.GetSize(&size);
1205
1206 if (!strncasecmp(type, "text/", 5) && size > 0) {
1207 len += size;
1208 char *text = (char *)malloc(size);
1209 if (text == NULL__null) {
1210 puts("no memory!");
1211 return;
1212 }
1213 if (file.Read(text, size) < B_OK((int)0)) {
1214 puts("could not read from file");
1215 free(text);
1216 continue;
1217 }
1218 if (!inserted) {
1219 GetSelection(&start, &end);
1220 Delete();
1221 inserted = true;
1222 }
1223
1224 int32 offset = 0;
1225 for (int32 loop = 0; loop < size; loop++) {
1226 if (text[loop] == '\n') {
1227 Insert(&text[offset], loop - offset + 1);
1228 offset = loop + 1;
1229 } else if (text[loop] == '\r') {
1230 text[loop] = '\n';
1231 Insert(&text[offset], loop - offset + 1);
1232 if ((loop + 1 < size)
1233 && (text[loop + 1] == '\n'))
1234 loop++;
1235 offset = loop + 1;
1236 }
1237 }
1238 free(text);
1239 } else {
1240 isEnclosure = true;
1241 message.AddRef("refs", &ref);
1242 }
1243 }
1244 }
1245
1246 if (index == 1) {
1247 // message doesn't contain any refs - maybe the parent class likes it
1248 BTextView::MessageReceived(msg);
1249 break;
1250 }
1251
1252 if (inserted)
1253 Select(start, start + len);
1254 if (isEnclosure)
1255 Window()->PostMessage(&message, Window());
1256 break;
1257 }
1258
1259 case M_HEADER:
1260 msg->FindBool("header", &fHeader);
1261 SetText(NULL__null);
1262 LoadMessage(fMail, false, NULL__null);
1263 break;
1264
1265 case M_RAW:
1266 StopLoad();
1267
1268 msg->FindBool("raw", &fRaw);
1269 SetText(NULL__null);
1270 LoadMessage(fMail, false, NULL__null);
1271 break;
1272
1273 case M_SELECT:
1274 if (IsSelectable())
1275 Select(0, TextLength());
1276 break;
1277
1278 case M_SAVE:
1279 Save(msg);
1280 break;
1281
1282 case B_NODE_MONITOR:
1283 {
1284 int32 opcode;
1285 if (msg->FindInt32("opcode", &opcode) == B_NO_ERROR((int)0)) {
1286 dev_t device;
1287 if (msg->FindInt32("device", &device) < B_OK((int)0))
1288 break;
1289 ino_t inode;
1290 if (msg->FindInt64("node", &inode) < B_OK((int)0))
1291 break;
1292
1293 hyper_text *enclosure;
1294 for (int32 index = 0;
1295 (enclosure = (hyper_text *)fEnclosures->ItemAt(index++)) != NULL__null;) {
1296 if (device == enclosure->node.device
1297 && inode == enclosure->node.node) {
1298 if (opcode == B_ENTRY_REMOVED2) {
1299 enclosure->saved = false;
1300 enclosure->have_ref = false;
1301 } else if (opcode == B_ENTRY_MOVED3) {
1302 enclosure->ref.device = device;
1303 msg->FindInt64("to directory", &enclosure->ref.directory);
1304
1305 const char *name;
1306 msg->FindString("name", &name);
1307 enclosure->ref.set_name(name);
1308 }
1309 break;
1310 }
1311 }
1312 }
1313 break;
1314 }
1315
1316 //
1317 // Tracker has responded to a BMessage that was dragged out of
1318 // this email message. It has created a file for us, we just have to
1319 // put the stuff in it.
1320 //
1321 case B_COPY_TARGET:
1322 {
1323 BMessage data;
1324 if (msg->FindMessage("be:originator-data", &data) == B_OK((int)0)) {
1325 entry_ref directory;
1326 const char *name;
1327 hyper_text *enclosure;
1328
1329 if (data.FindPointer("enclosure", (void **)&enclosure) == B_OK((int)0)
1330 && msg->FindString("name", &name) == B_OK((int)0)
1331 && msg->FindRef("directory", &directory) == B_OK((int)0)) {
1332 switch (enclosure->type) {
1333 case TYPE_ENCLOSURE:
1334 case TYPE_BE_ENCLOSURE:
1335 {
1336 //
1337 // Enclosure. Decode the data and write it out.
1338 //
1339 BMessage saveMsg(M_SAVE);
1340 saveMsg.AddString("name", name);
1341 saveMsg.AddRef("directory", &directory);
1342 saveMsg.AddPointer("enclosure", enclosure);
1343 Save(&saveMsg, false);
1344 break;
1345 }
1346
1347 case TYPE_URL:
1348 {
1349 const char *replyType;
1350 if (msg->FindString("be:filetypes", &replyType) != B_OK((int)0))
1351 // drag recipient didn't ask for any specific type,
1352 // create a bookmark file as default
1353 replyType = "application/x-vnd.Be-bookmark";
1354
1355 BDirectory dir(&directory);
1356 BFile file(&dir, name, B_READ_WRITE0x0002);
1357 if (file.InitCheck() == B_OK((int)0)) {
1358 if (strcmp(replyType, "application/x-vnd.Be-bookmark") == 0) {
1359 // we got a request to create a bookmark, stuff
1360 // it with the url attribute
1361 file.WriteAttr("META:url", B_STRING_TYPE, 0,
1362 enclosure->name, strlen(enclosure->name) + 1);
1363 } else if (strcasecmp(replyType, "text/plain") == 0) {
1364 // create a plain text file, stuff it with
1365 // the url as text
1366 file.Write(enclosure->name, strlen(enclosure->name));
1367 }
1368
1369 BNodeInfo fileInfo(&file);
1370 fileInfo.SetType(replyType);
1371 }
1372 break;
1373 }
1374
1375 case TYPE_MAILTO:
1376 {
1377 //
1378 // Add some attributes to the already created
1379 // person file. Strip out the 'mailto:' if
1380 // possible.
1381 //
1382 char *addrStart = enclosure->name;
1383 while (true) {
1384 if (*addrStart == ':') {
1385 addrStart++;
1386 break;
1387 }
1388
1389 if (*addrStart == '\0') {
1390 addrStart = enclosure->name;
1391 break;
1392 }
1393
1394 addrStart++;
1395 }
1396
1397 const char *replyType;
1398 if (msg->FindString("be:filetypes", &replyType) != B_OK((int)0))
1399 // drag recipient didn't ask for any specific type,
1400 // create a bookmark file as default
1401 replyType = "application/x-vnd.Be-bookmark";
1402
1403 BDirectory dir(&directory);
1404 BFile file(&dir, name, B_READ_WRITE0x0002);
1405 if (file.InitCheck() == B_OK((int)0)) {
1406 if (!strcmp(replyType, "application/x-person")) {
1407 // we got a request to create a bookmark, stuff
1408 // it with the address attribute
1409 file.WriteAttr("META:email", B_STRING_TYPE, 0,
1410 addrStart, strlen(enclosure->name) + 1);
1411 } else if (!strcasecmp(replyType, "text/plain")) {
1412 // create a plain text file, stuff it with the
1413 // email as text
1414 file.Write(addrStart, strlen(addrStart));
1415 }
1416
1417 BNodeInfo fileInfo(&file);
1418 fileInfo.SetType(replyType);
1419 }
1420 break;
1421 }
1422 }
1423 } else {
1424 //
1425 // Assume this is handled by BTextView...
1426 // (Probably drag clipping.)
1427 //
1428 BTextView::MessageReceived(msg);
1429 }
1430 }
1431 break;
1432 }
1433
1434 case B_INPUT_METHOD_EVENT:
1435 {
1436 int32 im_op;
1437 if (msg->FindInt32("be:opcode", &im_op) == B_OK((int)0)){
1438 switch (im_op) {
1439 case B_INPUT_METHOD_STARTED:
1440 fInputMethodUndoState.replace = true;
1441 fInputMethodUndoState.active = true;
1442 break;
1443 case B_INPUT_METHOD_STOPPED:
1444 fInputMethodUndoState.active = false;
1445 if (fInputMethodUndoBuffer.CountItems() > 0) {
1446 KUndoItem *undo = fInputMethodUndoBuffer.ItemAt(
1447 fInputMethodUndoBuffer.CountItems() - 1);
1448 if (undo->History == K_INSERTED){
1449 fUndoBuffer.MakeNewUndoItem();
1450 fUndoBuffer.AddUndo(undo->RedoText, undo->Length,
1451 undo->Offset, undo->History, undo->CursorPos);
1452 fUndoBuffer.MakeNewUndoItem();
1453 }
1454 fInputMethodUndoBuffer.MakeEmpty();
1455 }
1456 break;
1457 case B_INPUT_METHOD_CHANGED:
1458 fInputMethodUndoState.active = true;
1459 break;
1460 case B_INPUT_METHOD_LOCATION_REQUEST:
1461 fInputMethodUndoState.active = true;
1462 break;
1463 }
1464 }
1465 BTextView::MessageReceived(msg);
1466 break;
1467 }
1468
1469 case M_REDO:
1470 Redo();
1471 break;
1472
1473 default:
1474 BTextView::MessageReceived(msg);
1475 }
1476}
1477
1478
1479void
1480TTextView::MouseDown(BPoint where)
1481{
1482 if (IsEditable()) {
1483 BPoint point;
1484 uint32 buttons;
1485 GetMouse(&point, &buttons);
1486 if (gDictCount && (buttons == B_SECONDARY_MOUSE_BUTTON)) {
1487 int32 offset, start, end, length;
1488 const char *text = Text();
1489 offset = OffsetAt(where);
1490 if (isalpha(text[offset])(__ctype_b[(int)((text[offset]))] & (unsigned short int)_ISalpha
)
) {
1491 length = TextLength();
1492
1493 //Find start and end of word
1494 //FindSpellBoundry(length, offset, &start, &end);
1495
1496 char c;
1497 bool isAlpha, isApost, isCap;
1498 int32 first;
1499
1500 for (first = offset;
1501 (first >= 0) && (((c = text[first]) == '\'') || isalpha(c)(__ctype_b[(int)((c))] & (unsigned short int)_ISalpha));
1502 first--) {}
1503 isCap = isupper(text[++first])(__ctype_b[(int)((text[++first]))] & (unsigned short int)
_ISupper)
;
1504
1505 for (start = offset, c = text[start], isAlpha = isalpha(c)(__ctype_b[(int)((c))] & (unsigned short int)_ISalpha), isApost = (c=='\'');
1506 (start >= 0) && (isAlpha || (isApost
1507 && (((c = text[start+1]) != 's') || !isCap) && isalpha(c)(__ctype_b[(int)((c))] & (unsigned short int)_ISalpha)
1508 && isalpha(text[start-1])(__ctype_b[(int)((text[start-1]))] & (unsigned short int)
_ISalpha)
));
1509 start--, c = text[start], isAlpha = isalpha(c)(__ctype_b[(int)((c))] & (unsigned short int)_ISalpha), isApost = (c == '\'')) {}
1510 start++;
1511
1512 for (end = offset, c = text[end], isAlpha = isalpha(c)(__ctype_b[(int)((c))] & (unsigned short int)_ISalpha), isApost = (c == '\'');
1513 (end < length) && (isAlpha || (isApost
1514 && (((c = text[end + 1]) != 's') || !isCap) && isalpha(c)(__ctype_b[(int)((c))] & (unsigned short int)_ISalpha)));
1515 end++, c = text[end], isAlpha = isalpha(c)(__ctype_b[(int)((c))] & (unsigned short int)_ISalpha), isApost = (c == '\'')) {}
1516
1517 length = end - start;
1518 BString srcWord;
1519 srcWord.SetTo(text + start, length);
1520
1521 bool foundWord = false;
1522 BList matches;
1523 BString *string;
1524
1525 BMenuItem *menuItem;
1526 BPopUpMenu menu("Words", false, false);
1527
1528 for (int32 i = 0; i < gDictCount; i++)
1529 gWords[i]->FindBestMatches(&matches,
1530 srcWord.String());
1531
1532 if (matches.CountItems()) {
1533 sort_word_list(&matches, srcWord.String());
1534 for (int32 i = 0; (string = (BString *)matches.ItemAt(i)) != NULL__null; i++) {
1535 menu.AddItem((menuItem = new BMenuItem(string->String(), NULL__null)));
1536 if (!strcasecmp(string->String(), srcWord.String())) {
1537 menuItem->SetEnabled(false);
1538 foundWord = true;
1539 }
1540 delete string;
1541 }
1542 } else {
1543 menuItem = new BMenuItem(B_TRANSLATE("No matches")BLocaleRoster::Default()->GetCatalog()->GetString(("No matches"
), "Mail")
, NULL__null);
1544 menuItem->SetEnabled(false);
1545 menu.AddItem(menuItem);
1546 }
1547
1548 BMenuItem *addItem = NULL__null;
1549 if (!foundWord && gUserDict >= 0) {
1550 menu.AddSeparatorItem();
1551 addItem = new BMenuItem(B_TRANSLATE("Add")BLocaleRoster::Default()->GetCatalog()->GetString(("Add"
), "Mail")
, NULL__null);
1552 menu.AddItem(addItem);
1553 }
1554
1555 point = ConvertToScreen(where);
1556 if ((menuItem = menu.Go(point, false, false)) != NULL__null) {
1557 if (menuItem == addItem) {
1558 BString newItem(srcWord.String());
1559 newItem << "\n";
1560 gWords[gUserDict]->InitIndex();
1561 gExactWords[gUserDict]->InitIndex();
1562 gUserDictFile->Write(newItem.String(), newItem.Length());
1563 gWords[gUserDict]->BuildIndex();
1564 gExactWords[gUserDict]->BuildIndex();
1565
1566 if (fSpellCheck)
1567 CheckSpelling(0, TextLength());
1568 } else {
1569 int32 len = strlen(menuItem->Label());
1570 Select(start, start);
1571 Delete(start, end);
1572 Insert(start, menuItem->Label(), len);
1573 Select(start+len, start+len);
1574 }
1575 }
1576 }
1577 return;
1578 } else if (fSpellCheck && IsEditable()) {
1579 int32 start, end;
1580
1581 GetSelection(&start, &end);
1582 FindSpellBoundry(1, start, &start, &end);
1583 CheckSpelling(start, end);
1584 }
1585 } else {
1586 // is not editable, look for enclosures/links
1587
1588 int32 clickOffset = OffsetAt(where);
1589 int32 items = fEnclosures->CountItems();
1590 for (int32 loop = 0; loop < items; loop++) {
1591 hyper_text *enclosure = (hyper_text*) fEnclosures->ItemAt(loop);
1592 if (clickOffset < enclosure->text_start || clickOffset >= enclosure->text_end)
1593 continue;
1594
1595 //
1596 // The user is clicking on this attachment
1597 //
1598
1599 int32 start;
1600 int32 finish;
1601 Select(enclosure->text_start, enclosure->text_end);
1602 GetSelection(&start, &finish);
1603 Window()->UpdateIfNeeded();
1604
1605 bool drag = false;
1606 bool held = false;
1607 uint32 buttons = 0;
1608 if (Window()->CurrentMessage()) {
1609 Window()->CurrentMessage()->FindInt32("buttons",
1610 (int32 *) &buttons);
1611 }
1612
1613 //
1614 // If this is the primary button, wait to see if the user is going
1615 // to single click, hold, or drag.
1616 //
1617 if (buttons != B_SECONDARY_MOUSE_BUTTON) {
1618 BPoint point = where;
1619 bigtime_t popupDelay;
1620 get_click_speed(&popupDelay);
1621 popupDelay *= 2;
1622 popupDelay += system_time();
1623 while (buttons && abs((int)(point.x - where.x)) < 4
1624 && abs((int)(point.y - where.y)) < 4
1625 && system_time() < popupDelay) {
1626 snooze(10000);
1627 GetMouse(&point, &buttons);
1628 }
1629
1630 if (system_time() < popupDelay) {
1631 //
1632 // The user either dragged this or released the button.
1633 // check if it was dragged.
1634 //
1635 if (!(abs((int)(point.x - where.x)) < 4
1636 && abs((int)(point.y - where.y)) < 4) && buttons)
1637 drag = true;
1638 } else {
1639 //
1640 // The user held the button down.
1641 //
1642 held = true;
1643 }
1644 }
1645
1646 //
1647 // If the user has right clicked on this menu,
1648 // or held the button down on it for a while,
1649 // pop up a context menu.
1650 //
1651 if (buttons == B_SECONDARY_MOUSE_BUTTON || held) {
1652 //
1653 // Right mouse click... Display a menu
1654 //
1655 BPoint point = where;
1656 ConvertToScreen(&point);
1657
1658 BMenuItem *item;
1659 if ((enclosure->type != TYPE_ENCLOSURE)
1660 && (enclosure->type != TYPE_BE_ENCLOSURE))
1661 item = fLinkMenu->Go(point, true);
1662 else
1663 item = fEnclosureMenu->Go(point, true);
1664
1665 BMessage *msg;
1666 if (item && (msg = item->Message()) != NULL__null) {
1667 if (msg->what == M_SAVE) {
1668 if (fPanel)
1669 fPanel->SetEnclosure(enclosure);
1670 else {
1671 fPanel = new TSavePanel(enclosure, this);
1672 fPanel->Window()->Show();
1673 }
1674 } else if (msg->what == M_COPY) {
1675 // copy link location to clipboard
1676
1677 if (be_clipboard->Lock()) {
1678 be_clipboard->Clear();
1679
1680 BMessage *clip;
1681 if ((clip = be_clipboard->Data()) != NULL__null) {
1682 clip->AddData("text/plain", B_MIME_TYPE,
1683 enclosure->name, strlen(enclosure->name));
1684 be_clipboard->Commit();
1685 }
1686 be_clipboard->Unlock();
1687 }
1688 } else
1689 Open(enclosure);
1690 }
1691 } else {
1692 //
1693 // Left button. If the user single clicks, open this link.
1694 // Otherwise, initiate a drag.
1695 //
1696 if (drag) {
1697 BMessage dragMessage(B_SIMPLE_DATA);
1698 dragMessage.AddInt32("be:actions", B_COPY_TARGET);
1699 dragMessage.AddString("be:types", B_FILE_MIME_TYPE);
1700 switch (enclosure->type) {
1701 case TYPE_BE_ENCLOSURE:
1702 case TYPE_ENCLOSURE:
1703 //
1704 // Attachment. The type is specified in the message.
1705 //
1706 dragMessage.AddString("be:types", B_FILE_MIME_TYPE);
1707 dragMessage.AddString("be:filetypes",
1708 enclosure->content_type ? enclosure->content_type : "");
1709 dragMessage.AddString("be:clip_name", enclosure->name);
1710 break;
1711
1712 case TYPE_URL:
1713 //
1714 // URL. The user can drag it into the tracker to
1715 // create a bookmark file.
1716 //
1717 dragMessage.AddString("be:types", B_FILE_MIME_TYPE);
1718 dragMessage.AddString("be:filetypes",
1719 "application/x-vnd.Be-bookmark");
1720 dragMessage.AddString("be:filetypes", "text/plain");
1721 dragMessage.AddString("be:clip_name", "Bookmark");
1722
1723 dragMessage.AddString("be:url", enclosure->name);
1724 break;
1725
1726 case TYPE_MAILTO:
1727 //
1728 // Mailto address. The user can drag it into the
1729 // tracker to create a people file.
1730 //
1731 dragMessage.AddString("be:types", B_FILE_MIME_TYPE);
1732 dragMessage.AddString("be:filetypes",
1733 "application/x-person");
1734 dragMessage.AddString("be:filetypes", "text/plain");
1735 dragMessage.AddString("be:clip_name", "Person");
1736
1737 dragMessage.AddString("be:email", enclosure->name);
1738 break;
1739
1740 default:
1741 //
1742 // Otherwise it doesn't have a type that I know how
1743 // to save. It won't have any types and if any
1744 // program wants to accept it, more power to them.
1745 // (tracker won't.)
1746 //
1747 dragMessage.AddString("be:clip_name", "Hyperlink");
1748 }
1749
1750 BMessage data;
1751 data.AddPointer("enclosure", enclosure);
1752 dragMessage.AddMessage("be:originator-data", &data);
1753
1754 BRegion selectRegion;
1755 GetTextRegion(start, finish, &selectRegion);
1756 DragMessage(&dragMessage, selectRegion.Frame(), this);
1757 } else {
1758 //
1759 // User Single clicked on the attachment. Open it.
1760 //
1761 Open(enclosure);
1762 }
1763 }
1764 return;
1765 }
1766 }
1767 BTextView::MouseDown(where);
1768}
1769
1770
1771void
1772TTextView::MouseMoved(BPoint where, uint32 code, const BMessage *msg)
1773{
1774 int32 start = OffsetAt(where);
1775
1776 for (int32 loop = fEnclosures->CountItems(); loop-- > 0;) {
1777 hyper_text *enclosure = (hyper_text *)fEnclosures->ItemAt(loop);
1778 if ((start >= enclosure->text_start) && (start < enclosure->text_end)) {
1779 if (!fCursor)
1780 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
1781 fCursor = true;
1782 return;
1783 }
1784 }
1785
1786 if (fCursor) {
1787 SetViewCursor(B_CURSOR_I_BEAM);
1788 fCursor = false;
1789 }
1790
1791 BTextView::MouseMoved(where, code, msg);
1792}
1793
1794
1795void
1796TTextView::ClearList()
1797{
1798 hyper_text *enclosure;
1799 while ((enclosure = (hyper_text *)fEnclosures->FirstItem()) != NULL__null) {
1800 fEnclosures->RemoveItem(enclosure);
1801
1802 if (enclosure->name)
1803 free(enclosure->name);
1804 if (enclosure->content_type)
1805 free(enclosure->content_type);
1806 if (enclosure->encoding)
1807 free(enclosure->encoding);
1808 if (enclosure->have_ref && !enclosure->saved) {
1809 BEntry entry(&enclosure->ref);
1810 entry.Remove();
1811 }
1812
1813 watch_node(&enclosure->node, B_STOP_WATCHING, this);
1814 free(enclosure);
1815 }
1816}
1817
1818
1819void
1820TTextView::LoadMessage(BEmailMessage *mail, bool quoteIt, const char *text)
1821{
1822 StopLoad();
1823
1824 fMail = mail;
1825
1826 ClearList();
1827
1828 MakeSelectable(true);
1829 MakeEditable(false);
1830 if (text)
1831 Insert(text, strlen(text));
1832
1833 //attr_info attrInfo;
1834 TTextView::Reader *reader = new TTextView::Reader(fHeader, fRaw, quoteIt, fIncoming,
1835 text != NULL__null, true,
1836 // I removed the following, because I absolutely can't imagine why it's
1837 // there (the mail kit should be able to deal with non-compliant mails)
1838 // -- axeld.
1839 // fFile->GetAttrInfo(B_MAIL_ATTR_MIME, &attrInfo) == B_OK,
1840 this, mail, fEnclosures, fStopSem);
1841
1842 resume_thread(fThread = spawn_thread(Reader::Run, "reader", B_NORMAL_PRIORITY10, reader));
1843}
1844
1845
1846void
1847TTextView::Open(hyper_text *enclosure)
1848{
1849 switch (enclosure->type) {
1850 case TYPE_URL:
1851 {
1852 const struct {const char *urlType, *handler; } handlerTable[] = {
1853 {"http", B_URL_HTTP},
1854 {"https", B_URL_HTTPS},
1855 {"ftp", B_URL_FTP},
1856 {"gopher", B_URL_GOPHER},
1857 {"mailto", B_URL_MAILTO},
1858 {"news", B_URL_NEWS},
1859 {"nntp", B_URL_NNTP},
1860 {"telnet", B_URL_TELNET},
1861 {"rlogin", B_URL_RLOGIN},
1862 {"tn3270", B_URL_TN3270},
1863 {"wais", B_URL_WAIS},
1864 {"file", B_URL_FILE},
1865 {NULL__null, NULL__null}
1866 };
1867 const char *handlerToLaunch = NULL__null;
1868
1869 const char *colonPos = strchr(enclosure->name, ':');
1870 if (colonPos) {
1871 int urlTypeLength = colonPos - enclosure->name;
1872
1873 for (int32 index = 0; handlerTable[index].urlType; index++) {
1874 if (!strncasecmp(enclosure->name,
1875 handlerTable[index].urlType, urlTypeLength)) {
1876 handlerToLaunch = handlerTable[index].handler;
1877 break;
1878 }
1879 }
1880 }
1881 if (handlerToLaunch) {
1882 entry_ref appRef;
1883 if (be_roster->FindApp(handlerToLaunch, &appRef) != B_OK((int)0))
1884 handlerToLaunch = NULL__null;
1885 }
1886 if (!handlerToLaunch)
1887 handlerToLaunch = "application/x-vnd.Be-Bookmark";
1888
1889 status_t result = be_roster->Launch(handlerToLaunch, 1, &enclosure->name);
1890 if (result != B_NO_ERROR((int)0) && result != B_ALREADY_RUNNING(((-2147483647 - 1) + 0x2000) + 4)) {
1891 beep();
1892 BAlert* alert = new BAlert("",
1893 B_TRANSLATE("There is no installed handler for "BLocaleRoster::Default()->GetCatalog()->GetString(("There is no installed handler for "
"URL links."), "Mail")
1894 "URL links.")BLocaleRoster::Default()->GetCatalog()->GetString(("There is no installed handler for "
"URL links."), "Mail")
, B_TRANSLATE("Sorry")BLocaleRoster::Default()->GetCatalog()->GetString(("Sorry"
), "Mail")
);
1895 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1896 alert->Go();
1897 }
1898 break;
1899 }
1900
1901 case TYPE_MAILTO:
1902 if (be_roster->Launch(B_MAIL_TYPE"text/x-email", 1, &enclosure->name) < B_OK((int)0)) {
1903 char *argv[] = {(char *)"Mail", enclosure->name};
1904 be_app->ArgvReceived(2, argv);
1905 }
1906 break;
1907
1908 case TYPE_ENCLOSURE:
1909 case TYPE_BE_ENCLOSURE:
1910 if (!enclosure->have_ref) {
1911 BPath path;
1912 if (find_directory(B_COMMON_TEMP_DIRECTORY, &path) == B_NO_ERROR((int)0)) {
1913 BDirectory dir(path.Path());
1914 if (dir.InitCheck() == B_NO_ERROR((int)0)) {
1915 char name[B_FILE_NAME_LENGTH(256)];
1916 char baseName[B_FILE_NAME_LENGTH(256)];
1917 strcpy(baseName, enclosure->name ? enclosure->name : "enclosure");
1918 strcpy(name, baseName);
1919 for (int32 index = 0; dir.Contains(name); index++)
1920 sprintf(name, "%s_%" B_PRId32"l" "d", baseName, index);
1921
1922 BEntry entry(path.Path());
1923 entry_ref ref;
1924 entry.GetRef(&ref);
1925
1926 BMessage save(M_SAVE);
1927 save.AddRef("directory", &ref);
1928 save.AddString("name", name);
1929 save.AddPointer("enclosure", enclosure);
1930 if (Save(&save) != B_NO_ERROR((int)0))
1931 break;
1932 enclosure->saved = false;
1933 }
1934 }
1935 }
1936
1937 BMessenger tracker("application/x-vnd.Be-TRAK");
1938 if (tracker.IsValid()) {
1939 BMessage openMsg(B_REFS_RECEIVED);
1940 openMsg.AddRef("refs", &enclosure->ref);
1941 tracker.SendMessage(&openMsg);
1942 }
1943 break;
1944 }
1945}
1946
1947
1948status_t
1949TTextView::Save(BMessage *msg, bool makeNewFile)
1950{
1951 const char *name;
1952 entry_ref ref;
1953 BFile file;
1954 BPath path;
1955 hyper_text *enclosure;
1956 status_t result = B_NO_ERROR((int)0);
1957 char entry_name[B_FILE_NAME_LENGTH(256)];
1958
1959 msg->FindString("name", &name);
1960 msg->FindRef("directory", &ref);
1961 msg->FindPointer("enclosure", (void **)&enclosure);
1962
1963 BDirectory dir;
1964 dir.SetTo(&ref);
1965 result = dir.InitCheck();
1966
1967 if (result == B_OK((int)0)) {
1968 if (makeNewFile) {
1969 //
1970 // Search for the file and delete it if it already exists.
1971 // (It may not, that's ok.)
1972 //
1973 BEntry entry;
1974 if (dir.FindEntry(name, &entry) == B_NO_ERROR((int)0))
1975 entry.Remove();
1976
1977 if ((enclosure->have_ref) && (!enclosure->saved)) {
1978 entry.SetTo(&enclosure->ref);
1979
1980 //
1981 // Added true arg and entry_name so MoveTo clobbers as
1982 // before. This may not be the correct behaviour, but
1983 // it's the preserved behaviour.
1984 //
1985 entry.GetName(entry_name);
1986 result = entry.MoveTo(&dir, entry_name, true);
1987 if (result == B_NO_ERROR((int)0)) {
1988 entry.Rename(name);
1989 entry.GetRef(&enclosure->ref);
1990 entry.GetNodeRef(&enclosure->node);
1991 enclosure->saved = true;
1992 return result;
1993 }
1994 }
1995
1996 if (result == B_NO_ERROR((int)0)) {
1997 result = dir.CreateFile(name, &file);
1998 if (result == B_NO_ERROR((int)0) && enclosure->content_type) {
1999 char type[B_MIME_TYPE_LENGTH(((256)-1) - 15)];
2000
2001 if (!strcasecmp(enclosure->content_type, "message/rfc822"))
2002 strcpy(type, "text/x-email");
2003 else if (!strcasecmp(enclosure->content_type, "message/delivery-status"))
2004 strcpy(type, "text/plain");
2005 else
2006 strcpy(type, enclosure->content_type);
2007
2008 BNodeInfo info(&file);
2009 info.SetType(type);
2010 }
2011 }
2012 } else {
2013 //
2014 // This file was dragged into the tracker or desktop. The file
2015 // already exists.
2016 //
2017 result = file.SetTo(&dir, name, B_WRITE_ONLY0x0001);
2018 }
2019 }
2020
2021 if (enclosure->component == NULL__null)
2022 result = B_ERROR(-1);
2023
2024 if (result == B_NO_ERROR((int)0)) {
2025 //
2026 // Write the data
2027 //
2028 enclosure->component->GetDecodedData(&file);
2029
2030 BEntry entry;
2031 dir.FindEntry(name, &entry);
2032 entry.GetRef(&enclosure->ref);
2033 enclosure->have_ref = true;
2034 enclosure->saved = true;
2035 entry.GetPath(&path);
2036 update_mime_info(path.Path(), false, true,
2037 !cistrcmp("application/octet-stream", enclosure->content_type ? enclosure->content_type : B_EMPTY_STRING));
2038 entry.GetNodeRef(&enclosure->node);
2039 watch_node(&enclosure->node, B_WATCH_NAME, this);
2040 }
2041
2042 if (result != B_NO_ERROR((int)0)) {
2043 beep();
2044 BAlert* alert = new BAlert("", B_TRANSLATE("An error occurred trying to save "BLocaleRoster::Default()->GetCatalog()->GetString(("An error occurred trying to save "
"the attachment."), "Mail")
2045 "the attachment.")BLocaleRoster::Default()->GetCatalog()->GetString(("An error occurred trying to save "
"the attachment."), "Mail")
, B_TRANSLATE("Sorry")BLocaleRoster::Default()->GetCatalog()->GetString(("Sorry"
), "Mail")
);
2046 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2047 alert->Go();
2048 }
2049
2050 return result;
2051}
2052
2053
2054void
2055TTextView::StopLoad()
2056{
2057 Window()->Unlock();
2058
2059 thread_info info;
2060 if (fThread != 0 && get_thread_info(fThread, &info)_get_thread_info((fThread), (&info), sizeof(*(&info))
)
== B_NO_ERROR((int)0)) {
2061 fStopLoading = true;
2062 acquire_sem(fStopSem);
2063 int32 result;
2064 wait_for_thread(fThread, &result);
2065 fThread = 0;
2066 release_sem(fStopSem);
2067 fStopLoading = false;
2068 }
2069
2070 Window()->Lock();
2071}
2072
2073
2074bool
2075TTextView::IsReaderThreadRunning()
2076{
2077 if (fThread == 0)
2078 return false;
2079
2080 thread_info info;
2081 for (int i = 5; i > 0; i--, usleep(100000))
2082 if (get_thread_info(fThread, &info)_get_thread_info((fThread), (&info), sizeof(*(&info))
)
!= B_OK((int)0))
2083 return false;
2084 return true;
2085}
2086
2087
2088void
2089TTextView::AddAsContent(BEmailMessage *mail, bool wrap, uint32 charset, mail_encoding encoding)
2090{
2091 if (mail == NULL__null)
2092 return;
2093
2094 int32 textLength = TextLength();
2095 const char *text = Text();
2096
2097 BTextMailComponent *body = mail->Body();
2098 if (body == NULL__null) {
2099 if (mail->SetBody(body = new BTextMailComponent()) < B_OK((int)0))
2100 return;
2101 }
2102 body->SetEncoding(encoding, charset);
2103
2104 // Just add the text as a whole if we can, or ...
2105 if (!wrap) {
2106 body->AppendText(text);
2107 return;
2108 }
2109
2110 // ... do word wrapping.
2111
2112 BWindow *window = Window();
2113 char *saveText = strdup(text);
2114 BRect saveTextRect = TextRect();
2115
2116 // do this before we start messing with the fonts
2117 // the user will never know...
2118 window->DisableUpdates();
2119 Hide();
2120 BScrollBar *vScroller = ScrollBar(B_VERTICAL);
2121 BScrollBar *hScroller = ScrollBar(B_HORIZONTAL);
2122 if (vScroller != NULL__null)
2123 vScroller->SetTarget((BView *)NULL__null);
2124 if (hScroller != NULL__null)
2125 hScroller->SetTarget((BView *)NULL__null);
2126
2127 // Temporarily set the font to a fixed width font for line wrapping
2128 // calculations. If the font doesn't have as many of the symbols as
2129 // the preferred font, go back to using the user's preferred font.
2130
2131 bool *boolArray;
2132 int missingCharactersFixedWidth = 0;
2133 int missingCharactersPreferredFont = 0;
2134 int32 numberOfCharacters;
2135
2136 numberOfCharacters = BString(text).CountChars();
2137 if (numberOfCharacters > 0
2138 && (boolArray = (bool *)malloc(sizeof(bool) * numberOfCharacters)) != NULL__null) {
2139 memset(boolArray, 0, sizeof (bool) * numberOfCharacters);
2140 be_fixed_font->GetHasGlyphs(text, numberOfCharacters, boolArray);
2141 for (int i = 0; i < numberOfCharacters; i++) {
2142 if (!boolArray[i])
2143 missingCharactersFixedWidth += 1;
2144 }
2145
2146 memset(boolArray, 0, sizeof (bool) * numberOfCharacters);
2147 fFont.GetHasGlyphs(text, numberOfCharacters, boolArray);
2148 for (int i = 0; i < numberOfCharacters; i++) {
2149 if (!boolArray[i])
2150 missingCharactersPreferredFont += 1;
2151 }
2152
2153 free(boolArray);
2154 }
2155
2156 if (missingCharactersFixedWidth > missingCharactersPreferredFont)
2157 SetFontAndColor(0, textLength, &fFont);
2158 else // All things being equal, the fixed font is better for wrapping.
2159 SetFontAndColor(0, textLength, be_fixed_font);
2160
2161 // calculate a text rect that is 72 columns wide
2162 BRect newTextRect = saveTextRect;
2163 newTextRect.right = newTextRect.left + be_fixed_font->StringWidth("m") * 72;
2164 SetTextRect(newTextRect);
2165
2166 // hard-wrap, based on TextView's soft-wrapping
2167 int32 numLines = CountLines();
2168 bool spaceMoved = false;
2169 char *content = (char *)malloc(textLength + numLines * 72); // more we'll ever need
2170 if (content != NULL__null) {
2171 int32 contentLength = 0;
2172
2173 for (int32 i = 0; i < numLines; i++) {
2174 int32 startOffset = OffsetAt(i);
2175 if (spaceMoved) {
2176 startOffset++;
2177 spaceMoved = false;
2178 }
2179 int32 endOffset = OffsetAt(i + 1);
2180 int32 lineLength = endOffset - startOffset;
2181
2182 // quick hack to not break URLs into several parts
2183 for (int32 pos = startOffset; pos < endOffset; pos++) {
2184 size_t urlLength;
2185 uint8 type = CheckForURL(text + pos, urlLength);
2186 if (type != 0)
2187 pos += urlLength;
2188
2189 if (pos > endOffset) {
2190 // find first break character after the URL
2191 for (; text[pos]; pos++) {
2192 if (isalnum(text[pos])(__ctype_b[(int)((text[pos]))] & (unsigned short int)_ISalnum
)
|| isspace(text[pos])(__ctype_b[(int)((text[pos]))] & (unsigned short int)_ISspace
)
)
2193 break;
2194 }
2195 if (text[pos] && isspace(text[pos])(__ctype_b[(int)((text[pos]))] & (unsigned short int)_ISspace
)
&& text[pos] != '\n')
2196 pos++;
2197
2198 endOffset += pos - endOffset;
2199 lineLength = endOffset - startOffset;
2200
2201 // insert a newline (and the same number of quotes) after the
2202 // URL to make sure the rest of the text is properly wrapped
2203
2204 char buffer[64];
2205 if (text[pos] == '\n')
2206 buffer[0] = '\0';
2207 else
2208 strcpy(buffer, "\n");
2209
2210 size_t quoteLength;
2211 CopyQuotes(text + startOffset, lineLength, buffer + strlen(buffer), quoteLength);
2212
2213 Insert(pos, buffer, strlen(buffer));
2214 numLines = CountLines();
2215 text = Text();
2216 i++;
2217 }
2218 }
2219 if (text[endOffset - 1] != ' '
2220 && text[endOffset - 1] != '\n'
2221 && text[endOffset] == ' ') {
2222 // make sure spaces will be part of this line
2223 endOffset++;
2224 lineLength++;
2225 spaceMoved = true;
2226 }
2227
2228 memcpy(content + contentLength, text + startOffset, lineLength);
2229 contentLength += lineLength;
2230
2231 // add a newline to every line except for the ones
2232 // that already end in newlines, and the last line
2233 if ((text[endOffset - 1] != '\n') && (i < numLines - 1)) {
2234 content[contentLength++] = '\n';
2235
2236 // copy quote level of the first line
2237 size_t quoteLength;
2238 CopyQuotes(text + startOffset, lineLength, content + contentLength, quoteLength);
2239 contentLength += quoteLength;
2240 }
2241 }
2242 content[contentLength] = '\0';
2243
2244 body->AppendText(content);
2245 free(content);
2246 }
2247
2248 // reset the text rect and font
2249 SetTextRect(saveTextRect);
2250 SetText(saveText);
2251 free(saveText);
2252 SetFontAndColor(0, textLength, &fFont);
2253
2254 // should be OK to hook these back up now
2255 if (vScroller != NULL__null)
2256 vScroller->SetTarget(this);
2257 if (hScroller != NULL__null)
2258 hScroller->SetTarget(this);
2259
2260 Show();
2261 window->EnableUpdates();
2262}
2263
2264
2265// #pragma mark -
2266
2267
2268TTextView::Reader::Reader(bool header, bool raw, bool quote, bool incoming,
2269 bool stripHeader, bool mime, TTextView *view, BEmailMessage *mail,
2270 BList *list, sem_id sem)
2271 :
2272 fHeader(header),
2273 fRaw(raw),
2274 fQuote(quote),
2275 fIncoming(incoming),
2276 fStripHeader(stripHeader),
2277 fMime(mime),
2278 fView(view),
2279 fMail(mail),
2280 fEnclosures(list),
2281 fStopSem(sem)
2282{
2283}
2284
2285
2286bool
2287TTextView::Reader::ParseMail(BMailContainer *container,
2288 BTextMailComponent *ignore)
2289{
2290 int32 count = 0;
2291 for (int32 i = 0; i < container->CountComponents(); i++) {
2292 if (fView->fStopLoading)
2293 return false;
2294
2295 BMailComponent *component;
2296 if ((component = container->GetComponent(i)) == NULL__null) {
2297 if (fView->fStopLoading)
2298 return false;
2299
2300 hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text));
2301 if (enclosure == NULL__null)
2302 return false;
2303
2304 memset(enclosure, 0, sizeof(hyper_text));
2305
2306 enclosure->type = TYPE_ENCLOSURE;
2307
2308 const char *name = "\n<Attachment: could not handle>\n";
2309
2310 fView->GetSelection(&enclosure->text_start, &enclosure->text_end);
2311 enclosure->text_start++;
2312 enclosure->text_end += strlen(name) - 1;
2313
2314 Insert(name, strlen(name), true);
2315 fEnclosures->AddItem(enclosure);
2316 continue;
2317 }
2318
2319 count++;
2320 if (component == ignore)
2321 continue;
2322
2323 if (component->ComponentType() == B_MAIL_MULTIPART_CONTAINER) {
2324 BMIMEMultipartMailContainer *c = dynamic_cast<BMIMEMultipartMailContainer *>(container->GetComponent(i));
2325 ASSERT(c != NULL)(void)0;
2326
2327 if (!ParseMail(c, ignore))
2328 count--;
2329 } else if (fIncoming) {
2330 hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text));
2331 if (enclosure == NULL__null)
2332 return false;
2333
2334 memset(enclosure, 0, sizeof(hyper_text));
2335
2336 enclosure->type = TYPE_ENCLOSURE;
2337 enclosure->component = component;
2338
2339 BString name;
2340 char fileName[B_FILE_NAME_LENGTH(256)];
2341 strcpy(fileName, "untitled");
2342 if (BMailAttachment *attachment = dynamic_cast <BMailAttachment *> (component))
2343 attachment->FileName(fileName);
2344
2345 BPath path(fileName);
2346 enclosure->name = strdup(path.Leaf());
2347
2348 BMimeType type;
2349 component->MIMEType(&type);
2350 enclosure->content_type = strdup(type.Type());
2351
2352 char typeDescription[B_MIME_TYPE_LENGTH(((256)-1) - 15)];
2353 if (type.GetShortDescription(typeDescription) != B_OK((int)0))
2354 strcpy(typeDescription, type.Type() ? type.Type() : B_EMPTY_STRING);
2355
2356 name = "\n<";
2357 name.Append(B_TRANSLATE_COMMENT("Enclosure: %name% (Type: %type%)",BLocaleRoster::Default()->GetCatalog()->GetString(("Enclosure: %name% (Type: %type%)"
), "Mail", ("Don't translate the variables %name% and %type%."
))
2358 "Don't translate the variables %name% and %type%.")BLocaleRoster::Default()->GetCatalog()->GetString(("Enclosure: %name% (Type: %type%)"
), "Mail", ("Don't translate the variables %name% and %type%."
))
);
2359 name.Append(">\n");
2360 name.ReplaceFirst("%name%", enclosure->name);
2361 name.ReplaceFirst("%type%", typeDescription);
2362
2363 fView->GetSelection(&enclosure->text_start, &enclosure->text_end);
2364 enclosure->text_start++;
2365 enclosure->text_end += strlen(name.String()) - 1;
2366
2367 Insert(name.String(), name.Length(), true);
2368 fEnclosures->AddItem(enclosure);
2369 }
2370// default:
2371// {
2372// PlainTextBodyComponent *body = dynamic_cast<PlainTextBodyComponent *>(container->GetComponent(i));
2373// const char *text;
2374// if (body && (text = body->Text()) != NULL)
2375// Insert(text, strlen(text), false);
2376// }
2377 }
2378 return count > 0;
2379}
2380
2381
2382bool
2383TTextView::Reader::Process(const char *data, int32 data_len, bool isHeader)
2384{
2385 char line[522];
2386 int32 count = 0;
2387
2388 for (int32 loop = 0; loop < data_len; loop++) {
2389 if (fView->fStopLoading)
2390 return false;
2391
2392 if (fQuote && (!loop || (loop && data[loop - 1] == '\n'))) {
2393 strcpy(&line[count], QUOTE"> ");
2394 count += strlen(QUOTE"> ");
2395 }
2396 if (!fRaw && fIncoming && (loop < data_len - 7)) {
2397 size_t urlLength;
2398 BString url;
2399 uint8 type = CheckForURL(data + loop, urlLength, &url);
2400
2401 if (type) {
2402 if (!Insert(line, count, false, isHeader))
2403 return false;
2404 count = 0;
2405
2406 hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text));
2407 if (enclosure == NULL__null)
2408 return false;
2409
2410 memset(enclosure, 0, sizeof(hyper_text));
2411 fView->GetSelection(&enclosure->text_start,
2412 &enclosure->text_end);
2413 enclosure->type = type;
2414 enclosure->name = strdup(url.String());
2415 if (enclosure->name == NULL__null) {
2416 free(enclosure);
2417 return false;
2418 }
2419
2420 Insert(&data[loop], urlLength, true, isHeader);
2421 enclosure->text_end += urlLength;
2422 loop += urlLength - 1;
2423
2424 fEnclosures->AddItem(enclosure);
2425 continue;
2426 }
2427 }
2428 if (!fRaw && fMime && data[loop] == '=') {
2429 if ((loop) && (loop < data_len - 1) && (data[loop + 1] == '\r'))
2430 loop += 2;
2431 else
2432 line[count++] = data[loop];
2433 } else if (data[loop] != '\r')
2434 line[count++] = data[loop];
2435
2436 if (count > 511 || (count && loop == data_len - 1)) {
2437 if (!Insert(line, count, false, isHeader))
2438 return false;
2439 count = 0;
2440 }
2441 }
2442 return true;
2443}
2444
2445
2446bool
2447TTextView::Reader::Insert(const char *line, int32 count, bool isHyperLink,
2448 bool isHeader)
2449{
2450 if (!count)
2451 return true;
2452
2453 BFont font(fView->Font());
2454 TextRunArray style(count / 8 + 8);
2455
2456 if (fView->fColoredQuotes && !isHeader && !isHyperLink) {
2457 FillInQuoteTextRuns(fView, &fQuoteContext, line, count, font,
2458 &style.Array(), style.MaxEntries());
2459 } else {
2460 text_run_array &array = style.Array();
2461 array.count = 1;
2462 array.runs[0].offset = 0;
2463 if (isHeader) {
2464 array.runs[0].color = isHyperLink ? kHyperLinkColor : kHeaderColor;
2465 font.SetSize(font.Size() * 0.9);
2466 } else {
2467 array.runs[0].color = isHyperLink
2468 ? kHyperLinkColor : kNormalTextColor;
2469 }
2470 array.runs[0].font = font;
2471 }
2472
2473 if (!fView->Window()->Lock())
2474 return false;
2475
2476 fView->Insert(fView->TextLength(), line, count, &style.Array());
2477
2478 fView->Window()->Unlock();
2479 return true;
2480}
2481
2482
2483status_t
2484TTextView::Reader::Run(void *_this)
2485{
2486 Reader *reader = (Reader *)_this;
2487 TTextView *view = reader->fView;
2488 char *msg = NULL__null;
2489 off_t size = 0;
2490 int32 len = 0;
2491
2492 if (!reader->Lock())
2493 return B_INTERRUPTED((-2147483647 - 1) + 10);
2494
2495 BFile *file = dynamic_cast<BFile *>(reader->fMail->Data());
2496 if (file != NULL__null) {
2497 len = header_len(file);
2498
2499 if (reader->fHeader)
2500 size = len;
2501 if (reader->fRaw || !reader->fMime)
2502 file->GetSize(&size);
2503
2504 if (size != 0 && (msg = (char *)malloc(size)) == NULL__null)
2505 goto done;
2506 file->Seek(0, 0);
2507
2508 if (msg)
2509 size = file->Read(msg, size);
2510 }
2511
2512 // show the header?
2513 if (reader->fHeader && len) {
2514 // strip all headers except "From", "To", "Reply-To", "Subject", and "Date"
2515 if (reader->fStripHeader) {
2516 const char *header = msg;
2517 char *buffer = NULL__null;
2518
2519 while (strncmp(header, "\r\n", 2)) {
2520 const char *eol = header;
2521 while ((eol = strstr(eol, "\r\n")) != NULL__null && isspace(eol[2])(__ctype_b[(int)((eol[2]))] & (unsigned short int)_ISspace
)
)
2522 eol += 2;
2523 if (eol == NULL__null)
2524 break;
2525
2526 eol += 2; // CR+LF belong to the line
2527 size_t length = eol - header;
2528
2529 buffer = (char *)realloc(buffer, length + 1);
2530 if (buffer == NULL__null)
2531 goto done;
2532
2533 memcpy(buffer, header, length);
2534
2535 length = rfc2047_to_utf8(&buffer, &length, length);
2536
2537 if (!strncasecmp(header, "Reply-To: ", 10)
2538 || !strncasecmp(header, "To: ", 4)
2539 || !strncasecmp(header, "From: ", 6)
2540 || !strncasecmp(header, "Subject: ", 8)
2541 || !strncasecmp(header, "Date: ", 6))
2542 reader->Process(buffer, length, true);
2543
2544 header = eol;
2545 }
2546 free(buffer);
2547 reader->Process("\r\n", 2, true);
2548 }
2549 else if (!reader->Process(msg, len, true))
2550 goto done;
2551 }
2552
2553 if (reader->fRaw) {
2554 if (!reader->Process((const char *)msg + len, size - len))
2555 goto done;
2556 } else {
2557 //reader->fFile->Seek(0, 0);
2558 //BEmailMessage *mail = new BEmailMessage(reader->fFile);
2559 BEmailMessage *mail = reader->fMail;
2560
2561 // at first, insert the mail body
2562 BTextMailComponent *body = NULL__null;
2563 if (mail->BodyText() && !view->fStopLoading) {
2564 char *bodyText = const_cast<char *>(mail->BodyText());
2565 int32 bodyLength = strlen(bodyText);
2566 body = mail->Body();
2567 bool isHTML = false;
2568
2569 BMimeType type;
2570 if (body->MIMEType(&type) == B_OK((int)0) && type == "text/html") {
2571 // strip out HTML tags
2572 char *t = bodyText, *a, *end = bodyText + bodyLength;
2573 bodyText = (char *)malloc(bodyLength + 1);
2574 isHTML = true;
2575
2576 // TODO: is it correct to assume that the text is in Latin-1?
2577 // because if it isn't, the code below won't work correctly...
2578
2579 for (a = bodyText; t < end; t++) {
2580 int32 c = *t;
2581
2582 // compact spaces
2583 bool space = false;
2584 while (c && (c == ' ' || c == '\t')) {
2585 c = *(++t);
2586 space = true;
2587 }
2588 if (space) {
2589 c = ' ';
2590 t--;
2591 } else if (FilterHTMLTag(c, &t, end)) // the tag filter
2592 continue;
2593
2594 Unicode2UTF8(c, &a);
2595 }
2596
2597 *a = 0;
2598 bodyLength = strlen(bodyText);
2599 body = NULL__null; // to add the HTML text as enclosure
2600 }
2601 if (!reader->Process(bodyText, bodyLength))
2602 goto done;
2603
2604 if (isHTML)
2605 free(bodyText);
2606 }
2607
2608 if (!reader->ParseMail(mail, body))
2609 goto done;
2610
2611 //reader->fView->fMail = mail;
2612 }
2613
2614 if (!view->fStopLoading && view->Window()->Lock()) {
2615 view->Select(0, 0);
2616 view->MakeSelectable(true);
2617 if (!reader->fIncoming)
2618 view->MakeEditable(true);
2619
2620 view->Window()->Unlock();
2621 }
2622
2623done:
2624 // restore the reading position if available
2625 view->Window()->PostMessage(M_READ_POS);
2626
2627 reader->Unlock();
2628
2629 delete reader;
2630 free(msg);
2631
2632 return B_NO_ERROR((int)0);
2633}
2634
2635
2636status_t
2637TTextView::Reader::Unlock()
2638{
2639 return release_sem(fStopSem);
2640}
2641
2642
2643bool
2644TTextView::Reader::Lock()
2645{
2646 if (acquire_sem_etc(fStopSem, 1, B_TIMEOUT, 0) != B_NO_ERROR((int)0))
2647 return false;
2648
2649 return true;
2650}
2651
2652
2653//====================================================================
2654// #pragma mark -
2655
2656
2657TSavePanel::TSavePanel(hyper_text *enclosure, TTextView *view)
2658 : BFilePanel(B_SAVE_PANEL)
2659{
2660 fEnclosure = enclosure;
2661 fView = view;
2662 if (enclosure->name)
2663 SetSaveText(enclosure->name);
2664}
2665
2666
2667void
2668TSavePanel::SendMessage(const BMessenger * /* messenger */, BMessage *msg)
2669{
2670 const char *name = NULL__null;
2671 BMessage save(M_SAVE);
2672 entry_ref ref;
2673
2674 if ((!msg->FindRef("directory", &ref)) && (!msg->FindString("name", &name))) {
2675 save.AddPointer("enclosure", fEnclosure);
2676 save.AddString("name", name);
2677 save.AddRef("directory", &ref);
2678 fView->Window()->PostMessage(&save, fView);
2679 }
2680}
2681
2682
2683void
2684TSavePanel::SetEnclosure(hyper_text *enclosure)
2685{
2686 fEnclosure = enclosure;
2687 if (enclosure->name)
2688 SetSaveText(enclosure->name);
2689 else
2690 SetSaveText("");
2691
2692 if (!IsShowing())
2693 Show();
2694 Window()->Activate();
2695}
2696
2697
2698//--------------------------------------------------------------------
2699// #pragma mark -
2700
2701
2702void
2703TTextView::InsertText(const char *insertText, int32 length, int32 offset,
2704 const text_run_array *runs)
2705{
2706 ContentChanged();
2707
2708 // Undo function
2709
2710 int32 cursorPos, dummy;
2711 GetSelection(&cursorPos, &dummy);
2712
2713 if (fInputMethodUndoState.active) {
2714 // IMアクティブ時は、一旦別のバッファへ記憶
2715 fInputMethodUndoBuffer.AddUndo(insertText, length, offset, K_INSERTED, cursorPos);
2716 fInputMethodUndoState.replace = false;
2717 } else {
2718 if (fUndoState.replaced) {
2719 fUndoBuffer.AddUndo(insertText, length, offset, K_REPLACED, cursorPos);
2720 } else {
2721 if (length == 1 && insertText[0] == 0x0a)
2722 fUndoBuffer.MakeNewUndoItem();
2723
2724 fUndoBuffer.AddUndo(insertText, length, offset, K_INSERTED, cursorPos);
2725
2726 if (length == 1 && insertText[0] == 0x0a)
2727 fUndoBuffer.MakeNewUndoItem();
2728 }
2729 }
2730
2731 fUndoState.replaced = false;
2732 fUndoState.deleted = false;
2733
2734 struct text_runs : text_run_array { text_run _runs[1]; } style;
2735 if (runs == NULL__null && IsEditable()) {
2736 style.count = 1;
2737 style.runs[0].offset = 0;
2738 style.runs[0].font = fFont;
2739 style.runs[0].color = kNormalTextColor;
2740 runs = &style;
2741 }
2742
2743 BTextView::InsertText(insertText, length, offset, runs);
2744
2745 if (fSpellCheck && IsEditable())
2746 {
2747 UpdateSpellMarks(offset, length);
2748
2749 rgb_color color;
2750 GetFontAndColor(offset - 1, NULL__null, &color);
2751 const char *text = Text();
2752
2753 if (length > 1
2754 || isalpha(text[offset + 1])(__ctype_b[(int)((text[offset + 1]))] & (unsigned short int
)_ISalpha)
2755 || (!isalpha(text[offset])(__ctype_b[(int)((text[offset]))] & (unsigned short int)_ISalpha
)
&& text[offset] != '\'')
2756 || (color.red == kSpellTextColor.red
2757 && color.green == kSpellTextColor.green
2758 && color.blue == kSpellTextColor.blue))
2759 {
2760 int32 start, end;
2761 FindSpellBoundry(length, offset, &start, &end);
2762
2763 DSPELL(printf("Offset %ld, start %ld, end %ld\n", offset, start, end));;
2764 DSPELL(printf("\t\"%10.10s...\"\n", text + start));;
2765
2766 CheckSpelling(start, end);
2767 }
2768 }
2769}
2770
2771
2772void
2773TTextView::DeleteText(int32 start, int32 finish)
2774{
2775 ContentChanged();
2776
2777 // Undo function
2778 int32 cursorPos, dummy;
2779 GetSelection(&cursorPos, &dummy);
2780 if (fInputMethodUndoState.active) {
2781 if (fInputMethodUndoState.replace) {
2782 fUndoBuffer.AddUndo(&Text()[start], finish - start, start, K_DELETED, cursorPos);
2783 fInputMethodUndoState.replace = false;
2784 } else {
2785 fInputMethodUndoBuffer.AddUndo(&Text()[start], finish - start, start,
2786 K_DELETED, cursorPos);
2787 }
2788 } else
2789 fUndoBuffer.AddUndo(&Text()[start], finish - start, start, K_DELETED, cursorPos);
2790
2791 fUndoState.deleted = true;
2792 fUndoState.replaced = true;
2793
2794 BTextView::DeleteText(start, finish);
2795 if (fSpellCheck && IsEditable()) {
2796 UpdateSpellMarks(start, start - finish);
2797
2798 int32 s, e;
2799 FindSpellBoundry(1, start, &s, &e);
2800 CheckSpelling(s, e);
2801 }
2802}
2803
2804
2805void
2806TTextView::ContentChanged(void)
2807{
2808 BLooper *looper = Looper();
2809 if (looper == NULL__null)
2810 return;
2811
2812 BMessage msg(FIELD_CHANGED);
2813 msg.AddInt32("bitmask", FIELD_BODY);
2814 msg.AddPointer("source", this);
2815 looper->PostMessage(&msg);
2816}
2817
2818
2819void
2820TTextView::CheckSpelling(int32 start, int32 end, int32 flags)
2821{
2822 const char *text = Text();
2823 const char *next, *endPtr, *word = NULL__null;
2824 int32 wordLength = 0, wordOffset;
2825 int32 nextHighlight = start;
2826 BString testWord;
2827 bool isCap = false;
2828 bool isAlpha;
2829 bool isApost;
2830
2831 for (next = text + start, endPtr = text + end; next <= endPtr; next++) {
2832 //printf("next=%c\n", *next);
2833 // ToDo: this has to be refined to other languages...
2834 // Alpha signifies the start of a word
2835 isAlpha = isalpha(*next)(__ctype_b[(int)((*next))] & (unsigned short int)_ISalpha
)
;
2836 isApost = (*next == '\'');
2837 if (!word && isAlpha) {
2838 //printf("Found word start\n");
2839 word = next;
2840 wordLength++;
2841 isCap = isupper(*word)(__ctype_b[(int)((*word))] & (unsigned short int)_ISupper
)
;
2842 } else if (word && (isAlpha || isApost) && !(isApost && !isalpha(next[1])(__ctype_b[(int)((next[1]))] & (unsigned short int)_ISalpha
)
)
2843 && !(isCap && isApost && (next[1] == 's'))) {
2844 // Word continues check
2845 wordLength++;
2846 //printf("Word continues...\n");
2847 } else if (word) {
2848 // End of word reached
2849
2850 //printf("Word End\n");
2851 // Don't check single characters
2852 if (wordLength > 1) {
2853 bool isUpper = true;
2854
2855 // Look for all uppercase
2856 for (int32 i = 0; i < wordLength; i++) {
2857 if (word[i] == '\'')
2858 break;
2859
2860 if (islower(word[i])(__ctype_b[(int)((word[i]))] & (unsigned short int)_ISlower
)
) {
2861 isUpper = false;
2862 break;
2863 }
2864 }
2865
2866 // Don't check all uppercase words
2867 if (!isUpper) {
2868 bool foundMatch = false;
2869 wordOffset = word - text;
2870 testWord.SetTo(word, wordLength);
2871
2872 testWord = testWord.ToLower();
2873 DSPELL(printf("Testing: \"%s\"\n", testWord.String()));;
2874
2875 int32 key = -1;
2876 if (gDictCount)
2877 key = gExactWords[0]->GetKey(testWord.String());
2878
2879 // Search all dictionaries
2880 for (int32 i = 0; i < gDictCount; i++) {
2881 if (gExactWords[i]->Lookup(key) >= 0) {
2882 foundMatch = true;
2883 break;
2884 }
2885 }
2886
2887 if (!foundMatch) {
2888 if (flags & S_CLEAR_ERRORS)
2889 RemoveSpellMark(nextHighlight, wordOffset);
2890
2891 if (flags & S_SHOW_ERRORS)
2892 AddSpellMark(wordOffset, wordOffset + wordLength);
2893 } else if (flags & S_CLEAR_ERRORS)
2894 RemoveSpellMark(nextHighlight, wordOffset + wordLength);
2895
2896 nextHighlight = wordOffset + wordLength;
2897 }
2898 }
2899 // Reset state to looking for word
2900 word = NULL__null;
2901 wordLength = 0;
2902 }
2903 }
2904
2905 if (nextHighlight <= end
2906 && (flags & S_CLEAR_ERRORS) != 0
2907 && nextHighlight < TextLength())
2908 SetFontAndColor(nextHighlight, end, NULL__null, B_FONT_ALL, &kNormalTextColor);
2909}
2910
2911
2912void
2913TTextView::FindSpellBoundry(int32 length, int32 offset, int32 *_start, int32 *_end)
2914{
2915 int32 start, end, textLength;
2916 const char *text = Text();
2917 textLength = TextLength();
2918
2919 for (start = offset - 1; start >= 0
2920 && (isalpha(text[start])(__ctype_b[(int)((text[start]))] & (unsigned short int)_ISalpha
)
|| text[start] == '\''); start--) {}
2921
2922 start++;
2923
2924 for (end = offset + length; end < textLength
2925 && (isalpha(text[end])(__ctype_b[(int)((text[end]))] & (unsigned short int)_ISalpha
)
|| text[end] == '\''); end++) {}
2926
2927 *_start = start;
2928 *_end = end;
2929}
2930
2931
2932TTextView::spell_mark *
2933TTextView::FindSpellMark(int32 start, int32 end, spell_mark **_previousMark)
2934{
2935 spell_mark *lastMark = NULL__null;
2936
2937 for (spell_mark *spellMark = fFirstSpellMark; spellMark; spellMark = spellMark->next) {
2938 if (spellMark->start < end && spellMark->end > start) {
2939 if (_previousMark)
2940 *_previousMark = lastMark;
2941 return spellMark;
2942 }
2943
2944 lastMark = spellMark;
2945 }
2946 return NULL__null;
2947}
2948
2949
2950void
2951TTextView::UpdateSpellMarks(int32 offset, int32 length)
2952{
2953 DSPELL(printf("UpdateSpellMarks: offset = %ld, length = %ld\n", offset, length));;
2954
2955 spell_mark *spellMark;
2956 for (spellMark = fFirstSpellMark; spellMark; spellMark = spellMark->next) {
2957 DSPELL(printf("\tfound: %ld - %ld\n", spellMark->start, spellMark->end));;
2958
2959 if (spellMark->end < offset)
2960 continue;
2961
2962 if (spellMark->start > offset)
2963 spellMark->start += length;
2964
2965 spellMark->end += length;
2966
2967 DSPELL(printf("\t-> reset: %ld - %ld\n", spellMark->start, spellMark->end));;
2968 }
2969}
2970
2971
2972status_t
2973TTextView::AddSpellMark(int32 start, int32 end)
2974{
2975 DSPELL(printf("AddSpellMark: start = %ld, end = %ld\n", start, end));;
2976
2977 // check if there is already a mark for this passage
2978 spell_mark *spellMark = FindSpellMark(start, end);
2979 if (spellMark) {
2980 if (spellMark->start == start && spellMark->end == end) {
2981 DSPELL(printf("\tfound one\n"));;
2982 return B_OK((int)0);
2983 }
2984
2985 DSPELL(printf("\tremove old one\n"));;
2986 RemoveSpellMark(start, end);
2987 }
2988
2989 spellMark = (spell_mark *)malloc(sizeof(spell_mark));
2990 if (spellMark == NULL__null)
2991 return B_NO_MEMORY((-2147483647 - 1) + 0);
2992
2993 spellMark->start = start;
2994 spellMark->end = end;
2995 spellMark->style = RunArray(start, end);
2996
2997 // set the spell marks appearance
2998 BFont font(fFont);
2999 font.SetFace(B_BOLD_FACE | B_ITALIC_FACE);
3000 SetFontAndColor(start, end, &font, B_FONT_ALL, &kSpellTextColor);
3001
3002 // add it to the queue
3003 spellMark->next = fFirstSpellMark;
3004 fFirstSpellMark = spellMark;
3005
3006 return B_OK((int)0);
3007}
3008
3009
3010bool
3011TTextView::RemoveSpellMark(int32 start, int32 end)
3012{
3013 DSPELL(printf("RemoveSpellMark: start = %ld, end = %ld\n", start, end));;
3014
3015 // find spell mark
3016 spell_mark *lastMark = NULL__null;
3017 spell_mark *spellMark = FindSpellMark(start, end, &lastMark);
3018 if (spellMark == NULL__null) {
3019 DSPELL(printf("\tnot found!\n"));;
3020 return false;
3021 }
3022
3023 DSPELL(printf("\tfound: %ld - %ld\n", spellMark->start, spellMark->end));;
3024
3025 // dequeue the spell mark
3026 if (lastMark)
3027 lastMark->next = spellMark->next;
3028 else
3029 fFirstSpellMark = spellMark->next;
3030
3031 if (spellMark->start < start)
3032 start = spellMark->start;
3033 if (spellMark->end > end)
3034 end = spellMark->end;
3035
3036 // reset old text run array
3037 SetRunArray(start, end, spellMark->style);
3038
3039 free(spellMark->style);
3040 free(spellMark);
3041
3042 return true;
3043}
3044
3045
3046void
3047TTextView::RemoveSpellMarks()
3048{
3049 spell_mark *spellMark, *nextMark;
3050
3051 for (spellMark = fFirstSpellMark; spellMark; spellMark = nextMark) {
3052 nextMark = spellMark->next;
3053
3054 // reset old text run array
3055 SetRunArray(spellMark->start, spellMark->end, spellMark->style);
3056
3057 free(spellMark->style);
3058 free(spellMark);
3059 }
3060
3061 fFirstSpellMark = NULL__null;
3062}
3063
3064
3065void
3066TTextView::EnableSpellCheck(bool enable)
3067{
3068 if (fSpellCheck == enable)
3069 return;
3070
3071 fSpellCheck = enable;
3072 int32 textLength = TextLength();
3073 if (fSpellCheck) {
3074 // work-around for a bug in the BTextView class
3075 // which causes lots of flicker
3076 int32 start, end;
3077 GetSelection(&start, &end);
3078 if (start != end)
3079 Select(start, start);
3080
3081 CheckSpelling(0, textLength);
3082
3083 if (start != end)
3084 Select(start, end);
3085 }
3086 else
3087 RemoveSpellMarks();
3088}
3089
3090
3091void
3092TTextView::WindowActivated(bool flag)
3093{
3094 if (!flag) {
3095 // WindowActivated(false) は、IM も Inactive になり、そのまま確定される。
3096 // しかしこの場合、input_server が B_INPUT_METHOD_EVENT(B_INPUT_METHOD_STOPPED)
3097 // を送ってこないまま矛盾してしまうので、やむを得ずここでつじつまあわせ処理している。
3098 // OpenBeOSで修正されることを願って暫定処置としている。
3099 fInputMethodUndoState.active = false;
3100 // fInputMethodUndoBufferに溜まっている最後のデータがK_INSERTEDなら(確定)正規のバッファへ追加
3101 if (fInputMethodUndoBuffer.CountItems() > 0) {
3102 KUndoItem *item = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1);
3103 if (item->History == K_INSERTED) {
3104 fUndoBuffer.MakeNewUndoItem();
3105 fUndoBuffer.AddUndo(item->RedoText, item->Length, item->Offset,
3106 item->History, item->CursorPos);
3107 fUndoBuffer.MakeNewUndoItem();
3108 }
3109 fInputMethodUndoBuffer.MakeEmpty();
3110 }
3111 }
3112 BTextView::WindowActivated(flag);
3113}
3114
3115
3116void
3117TTextView::AddQuote(int32 start, int32 finish)
3118{
3119 BRect rect = Bounds();
3120
3121 int32 lineStart;
3122 GoToLine(CurrentLine());
3123 GetSelection(&lineStart, &lineStart);
3124
3125 // make sure that we're changing the whole last line, too
3126 int32 lineEnd = finish > lineStart ? finish - 1 : finish;
1
'?' condition is false
3127 {
3128 const char *text = Text();
3129 while (text[lineEnd] && text[lineEnd] != '\n')
2
Loop condition is false. Execution continues on line 3132
3130 lineEnd++;
3131 }
3132 Select(lineStart, lineEnd);
3133
3134 int32 textLength = lineEnd - lineStart;
3135 char *text = (char *)malloc(textLength + 1);
3136 if (text == NULL__null)
3
Taking false branch
3137 return;
3138
3139 GetText(lineStart, textLength, text);
3140
3141 int32 quoteLength = strlen(QUOTE"> ");
3142 int32 targetLength = 0;
3143 char *target = NULL__null;
3144 int32 lastLine = 0;
3145
3146 for (int32 index = 0; index < textLength; index++) {
4
Assuming 'index' is < 'textLength'
5
Loop condition is true. Entering loop body
8
Assuming 'index' is < 'textLength'
9
Loop condition is true. Entering loop body
3147 if (text[index] == '\n' || index == textLength - 1) {
3148 // add quote to this line
3149 int32 lineLength = index - lastLine + 1;
3150
3151 target = (char *)realloc(target, targetLength + lineLength + quoteLength);
6
Memory is allocated
10
Attempt to reallocate memory
3152 if (target == NULL__null) {
7
Taking false branch
11
Reallocation failed
12
Taking true branch
3153 // free the old buffer?
3154 free(text);
13
Potential leak of memory pointed to by 'target'
3155 return;
3156 }
3157
3158 // copy the quote sign
3159 memcpy(&target[targetLength], QUOTE"> ", quoteLength);
3160 targetLength += quoteLength;
3161
3162 // copy the rest of the line
3163 memcpy(&target[targetLength], &text[lastLine], lineLength);
3164 targetLength += lineLength;
3165
3166 lastLine = index + 1;
3167 }
3168 }
3169
3170 // replace with quoted text
3171 free(text);
3172 Delete();
3173
3174 if (fColoredQuotes) {
3175 const BFont *font = Font();
3176 TextRunArray style(targetLength / 8 + 8);
3177
3178 FillInQuoteTextRuns(NULL__null, NULL__null, target, targetLength, font,
3179 &style.Array(), style.MaxEntries());
3180 Insert(target, targetLength, &style.Array());
3181 } else
3182 Insert(target, targetLength);
3183
3184 free(target);
3185
3186 // redo the old selection (compute the new start if necessary)
3187 Select(start + quoteLength, finish + (targetLength - textLength));
3188
3189 ScrollTo(rect.LeftTop());
3190}
3191
3192
3193void
3194TTextView::RemoveQuote(int32 start, int32 finish)
3195{
3196 BRect rect = Bounds();
3197
3198 GoToLine(CurrentLine());
3199 int32 lineStart;
3200 GetSelection(&lineStart, &lineStart);
3201
3202 // make sure that we're changing the whole last line, too
3203 int32 lineEnd = finish > lineStart ? finish - 1 : finish;
3204 const char *text = Text();
3205 while (text[lineEnd] && text[lineEnd] != '\n')
3206 lineEnd++;
3207
3208 Select(lineStart, lineEnd);
3209
3210 int32 length = lineEnd - lineStart;
3211 char *target = (char *)malloc(length + 1);
3212 if (target == NULL__null)
3213 return;
3214
3215 int32 quoteLength = strlen(QUOTE"> ");
3216 int32 removed = 0;
3217 text += lineStart;
3218
3219 for (int32 index = 0; index < length;) {
3220 // find out the length of the current line
3221 int32 lineLength = 0;
3222 while (index + lineLength < length && text[lineLength] != '\n')
3223 lineLength++;
3224
3225 // include the newline to be part of this line
3226 if (text[lineLength] == '\n' && index + lineLength + 1 < length)
3227 lineLength++;
3228
3229 if (!strncmp(text, QUOTE"> ", quoteLength)) {
3230 // remove quote
3231 length -= quoteLength;
3232 removed += quoteLength;
3233
3234 lineLength -= quoteLength;
3235 text += quoteLength;
3236 }
3237
3238 if (lineLength == 0) {
3239 target[index] = '\0';
3240 break;
3241 }
3242
3243 memcpy(&target[index], text, lineLength);
3244
3245 text += lineLength;
3246 index += lineLength;
3247 }
3248
3249 if (removed) {
3250 Delete();
3251
3252 if (fColoredQuotes) {
3253 const BFont *font = Font();
3254 TextRunArray style(length / 8 + 8);
3255
3256 FillInQuoteTextRuns(NULL__null, NULL__null, target, length, font,
3257 &style.Array(), style.MaxEntries());
3258 Insert(target, length, &style.Array());
3259 } else
3260 Insert(target, length);
3261
3262 // redo old selection
3263 bool noSelection = start == finish;
3264
3265 if (start > lineStart + quoteLength)
3266 start -= quoteLength;
3267 else
3268 start = lineStart;
3269
3270 if (noSelection)
3271 finish = start;
3272 else
3273 finish -= removed;
3274 }
3275
3276 free(target);
3277
3278 Select(start, finish);
3279 ScrollTo(rect.LeftTop());
3280}
3281
3282
3283void
3284TTextView::Undo(BClipboard */*clipboard*/)
3285{
3286 if (fInputMethodUndoState.active)
3287 return;
3288
3289 int32 length, offset, cursorPos;
3290 undo_type history;
3291 char *text;
3292 status_t status;
3293
3294 status = fUndoBuffer.Undo(&text, &length, &offset, &history, &cursorPos);
3295 if (status == B_OK((int)0)) {
3296 fUndoBuffer.Off();
3297
3298 switch (history) {
3299 case K_INSERTED:
3300 BTextView::Delete(offset, offset + length);
3301 Select(offset, offset);
3302 break;
3303
3304 case K_DELETED:
3305 BTextView::Insert(offset, text, length);
3306 Select(offset, offset + length);
3307 break;
3308
3309 case K_REPLACED:
3310 BTextView::Delete(offset, offset + length);
3311 status = fUndoBuffer.Undo(&text, &length, &offset, &history, &cursorPos);
3312 if (status == B_OK((int)0) && history == K_DELETED) {
3313 BTextView::Insert(offset, text, length);
3314 Select(offset, offset + length);
3315 } else {
3316 ::beep();
3317 BAlert* alert = new BAlert("",
3318 B_TRANSLATE("Inconsistency occurred in the undo/redo "BLocaleRoster::Default()->GetCatalog()->GetString(("Inconsistency occurred in the undo/redo "
"buffer."), "Mail")
3319 "buffer.")BLocaleRoster::Default()->GetCatalog()->GetString(("Inconsistency occurred in the undo/redo "
"buffer."), "Mail")
, B_TRANSLATE("OK")BLocaleRoster::Default()->GetCatalog()->GetString(("OK"
), "Mail")
);
3320 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3321 alert->Go();
3322 }
3323 break;
3324 }
3325 ScrollToSelection();
3326 ContentChanged();
3327 fUndoBuffer.On();
3328 }
3329}
3330
3331
3332void
3333TTextView::Redo()
3334{
3335 if (fInputMethodUndoState.active)
3336 return;
3337
3338 int32 length, offset, cursorPos;
3339 undo_type history;
3340 char *text;
3341 status_t status;
3342 bool replaced;
3343
3344 status = fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced);
3345 if (status == B_OK((int)0)) {
3346 fUndoBuffer.Off();
3347
3348 switch (history) {
3349 case K_INSERTED:
3350 BTextView::Insert(offset, text, length);
3351 Select(offset, offset + length);
3352 break;
3353
3354 case K_DELETED:
3355 BTextView::Delete(offset, offset + length);
3356 if (replaced) {
3357 fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced);
3358 BTextView::Insert(offset, text, length);
3359 }
3360 Select(offset, offset + length);
3361 break;
3362
3363 case K_REPLACED:
3364 ::beep();
3365 BAlert* alert = new BAlert("",
3366 B_TRANSLATE("Inconsistency occurred in the undo/redo "BLocaleRoster::Default()->GetCatalog()->GetString(("Inconsistency occurred in the undo/redo "
"buffer."), "Mail")
3367 "buffer.")BLocaleRoster::Default()->GetCatalog()->GetString(("Inconsistency occurred in the undo/redo "
"buffer."), "Mail")
, B_TRANSLATE("OK")BLocaleRoster::Default()->GetCatalog()->GetString(("OK"
), "Mail")
);
3368 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3369 alert->Go();
3370 break;
3371 }
3372 ScrollToSelection();
3373 ContentChanged();
3374 fUndoBuffer.On();
3375 }
3376}
3377