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