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