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_TRANSLATION_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 BAlert* alert = new BAlert("", 759 B_TRANSLATE("An error occurred trying to open this " 760 "signature."), B_TRANSLATE("Sorry")); 761 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 762 alert->Go(); 763 } 764 break; 765 } 766 767 case M_FIND: 768 FindString(msg->FindString("findthis")); 769 break; 770 771 default: 772 BView::MessageReceived(msg); 773 } 774 } 775 776 777 void 778 TContentView::FindString(const char *str) 779 { 780 int32 finish; 781 int32 pass = 0; 782 int32 start = 0; 783 784 if (str == NULL) 785 return; 786 787 // 788 // Start from current selection or from the beginning of the pool 789 // 790 const char *text = fTextView->Text(); 791 int32 count = fTextView->TextLength(); 792 fTextView->GetSelection(&start, &finish); 793 if (start != finish) 794 start = finish; 795 if (!count || text == NULL) 796 return; 797 798 // 799 // Do the find 800 // 801 while (pass < 2) { 802 long found = -1; 803 char lc = tolower(str[0]); 804 char uc = toupper(str[0]); 805 for (long i = start; i < count; i++) { 806 if (text[i] == lc || text[i] == uc) { 807 const char *s = str; 808 const char *t = text + i; 809 while (*s && (tolower(*s) == tolower(*t))) { 810 s++; 811 t++; 812 } 813 if (*s == 0) { 814 found = i; 815 break; 816 } 817 } 818 } 819 820 // 821 // Select the text if it worked 822 // 823 if (found != -1) { 824 Window()->Activate(); 825 fTextView->Select(found, found + strlen(str)); 826 fTextView->ScrollToSelection(); 827 fTextView->MakeFocus(true); 828 return; 829 } 830 else if (start) { 831 start = 0; 832 text = fTextView->Text(); 833 count = fTextView->TextLength(); 834 pass++; 835 } else { 836 beep(); 837 return; 838 } 839 } 840 } 841 842 843 void 844 TContentView::Focus(bool focus) 845 { 846 if (fFocus != focus) { 847 fFocus = focus; 848 Draw(Frame()); 849 } 850 } 851 852 853 void 854 TContentView::FrameResized(float /* width */, float /* height */) 855 { 856 BRect r(fTextView->Bounds()); 857 r.OffsetTo(0, 0); 858 r.InsetBy(5, 5); 859 fTextView->SetTextRect(r); 860 } 861 862 863 //==================================================================== 864 // #pragma mark - 865 866 867 TTextView::TTextView(BRect frame, BRect text, bool incoming, TContentView *view, 868 BFont *font, bool showHeader, bool coloredQuotes) 869 : 870 BTextView(frame, "", text, B_FOLLOW_ALL, B_WILL_DRAW | B_NAVIGABLE), 871 872 fHeader(showHeader), 873 fColoredQuotes(coloredQuotes), 874 fReady(false), 875 fYankBuffer(NULL), 876 fLastPosition(-1), 877 fMail(NULL), 878 fFont(font), 879 fParent(view), 880 fStopLoading(false), 881 fThread(0), 882 fPanel(NULL), 883 fIncoming(incoming), 884 fSpellCheck(false), 885 fRaw(false), 886 fCursor(false), 887 fFirstSpellMark(NULL) 888 { 889 fStopSem = create_sem(1, "reader_sem"); 890 SetStylable(true); 891 892 fEnclosures = new BList(); 893 894 // Enclosure pop up menu 895 fEnclosureMenu = new BPopUpMenu("Enclosure", false, false); 896 fEnclosureMenu->SetFont(be_plain_font); 897 fEnclosureMenu->AddItem(new BMenuItem( 898 B_TRANSLATE("Save attachment" B_UTF8_ELLIPSIS), new BMessage(M_SAVE))); 899 fEnclosureMenu->AddItem(new BMenuItem(B_TRANSLATE("Open attachment"), 900 new BMessage(M_OPEN))); 901 902 // Hyperlink pop up menu 903 fLinkMenu = new BPopUpMenu("Link", false, false); 904 fLinkMenu->SetFont(be_plain_font); 905 fLinkMenu->AddItem(new BMenuItem(B_TRANSLATE("Open this link"), 906 new BMessage(M_OPEN))); 907 fLinkMenu->AddItem(new BMenuItem(B_TRANSLATE("Copy link location"), 908 new BMessage(M_COPY))); 909 910 SetDoesUndo(true); 911 912 //Undo function 913 fUndoBuffer.On(); 914 fInputMethodUndoBuffer.On(); 915 fUndoState.replaced = false; 916 fUndoState.deleted = false; 917 fInputMethodUndoState.active = false; 918 fInputMethodUndoState.replace = false; 919 } 920 921 922 TTextView::~TTextView() 923 { 924 ClearList(); 925 delete fPanel; 926 927 if (fYankBuffer) 928 free(fYankBuffer); 929 930 delete_sem(fStopSem); 931 } 932 933 934 void 935 TTextView::UpdateFont(const BFont* newFont) 936 { 937 fFont = *newFont; 938 939 // update the text run array safely with new font 940 text_run_array *runArray = RunArray(0, LONG_MAX); 941 for (int i = 0; i < runArray->count; i++) 942 runArray->runs[i].font = *newFont; 943 944 SetRunArray(0, LONG_MAX, runArray); 945 FreeRunArray(runArray); 946 } 947 948 949 void 950 TTextView::AttachedToWindow() 951 { 952 BTextView::AttachedToWindow(); 953 fFont.SetSpacing(B_FIXED_SPACING); 954 SetFontAndColor(&fFont); 955 956 if (fMail != NULL) { 957 LoadMessage(fMail, false, NULL); 958 if (fIncoming) 959 MakeEditable(false); 960 } 961 } 962 963 964 void 965 TTextView::KeyDown(const char *key, int32 count) 966 { 967 char raw; 968 int32 end; 969 int32 start; 970 uint32 mods; 971 BMessage *msg; 972 int32 textLen = TextLength(); 973 974 msg = Window()->CurrentMessage(); 975 mods = msg->FindInt32("modifiers"); 976 977 switch (key[0]) 978 { 979 case B_HOME: 980 if (IsSelectable()) 981 { 982 if (IsEditable()) 983 BTextView::KeyDown(key, count); 984 else 985 { 986 // scroll to the beginning 987 Select(0, 0); 988 ScrollToSelection(); 989 } 990 } 991 break; 992 993 case B_END: 994 if (IsSelectable()) 995 { 996 if (IsEditable()) 997 BTextView::KeyDown(key, count); 998 else 999 { 1000 // scroll to the end 1001 int32 length = TextLength(); 1002 Select(length, length); 1003 ScrollToSelection(); 1004 } 1005 } 1006 break; 1007 1008 case 0x02: // ^b - back 1 char 1009 if (IsSelectable()) 1010 { 1011 GetSelection(&start, &end); 1012 while (!IsInitialUTF8Byte(ByteAt(--start))) 1013 { 1014 if (start < 0) 1015 { 1016 start = 0; 1017 break; 1018 } 1019 } 1020 if (start >= 0) 1021 { 1022 Select(start, start); 1023 ScrollToSelection(); 1024 } 1025 } 1026 break; 1027 1028 case B_DELETE: 1029 if (IsSelectable()) 1030 { 1031 if ((key[0] == B_DELETE) || (mods & B_CONTROL_KEY)) // ^d 1032 { 1033 if (IsEditable()) 1034 { 1035 GetSelection(&start, &end); 1036 if (start != end) 1037 Delete(); 1038 else 1039 { 1040 for (end = start + 1; !IsInitialUTF8Byte(ByteAt(end)); end++) 1041 { 1042 if (end > textLen) 1043 { 1044 end = textLen; 1045 break; 1046 } 1047 } 1048 Select(start, end); 1049 Delete(); 1050 } 1051 } 1052 } 1053 else 1054 Select(textLen, textLen); 1055 ScrollToSelection(); 1056 } 1057 break; 1058 1059 case 0x05: // ^e - end of line 1060 if ((IsSelectable()) && (mods & B_CONTROL_KEY)) 1061 { 1062 if (CurrentLine() == CountLines() - 1) 1063 Select(TextLength(), TextLength()); 1064 else 1065 { 1066 GoToLine(CurrentLine() + 1); 1067 GetSelection(&start, &end); 1068 Select(start - 1, start - 1); 1069 } 1070 } 1071 break; 1072 1073 case 0x06: // ^f - forward 1 char 1074 if (IsSelectable()) 1075 { 1076 GetSelection(&start, &end); 1077 if (end > start) 1078 start = end; 1079 else 1080 { 1081 for (end = start + 1; !IsInitialUTF8Byte(ByteAt(end)); end++) 1082 { 1083 if (end > textLen) 1084 { 1085 end = textLen; 1086 break; 1087 } 1088 } 1089 start = end; 1090 } 1091 Select(start, start); 1092 ScrollToSelection(); 1093 } 1094 break; 1095 1096 case 0x0e: // ^n - next line 1097 if (IsSelectable()) 1098 { 1099 raw = B_DOWN_ARROW; 1100 BTextView::KeyDown(&raw, 1); 1101 } 1102 break; 1103 1104 case 0x0f: // ^o - open line 1105 if (IsEditable()) 1106 { 1107 GetSelection(&start, &end); 1108 Delete(); 1109 1110 char newLine = '\n'; 1111 Insert(&newLine, 1); 1112 Select(start, start); 1113 ScrollToSelection(); 1114 } 1115 break; 1116 1117 case B_PAGE_UP: 1118 if (mods & B_CONTROL_KEY) { // ^k kill text from cursor to e-o-line 1119 if (IsEditable()) { 1120 GetSelection(&start, &end); 1121 if ((start != fLastPosition) && (fYankBuffer)) { 1122 free(fYankBuffer); 1123 fYankBuffer = NULL; 1124 } 1125 fLastPosition = start; 1126 if (CurrentLine() < (CountLines() - 1)) { 1127 GoToLine(CurrentLine() + 1); 1128 GetSelection(&end, &end); 1129 end--; 1130 } 1131 else 1132 end = TextLength(); 1133 if (end < start) 1134 break; 1135 if (start == end) 1136 end++; 1137 Select(start, end); 1138 if (fYankBuffer) { 1139 fYankBuffer = (char *)realloc(fYankBuffer, 1140 strlen(fYankBuffer) + (end - start) + 1); 1141 GetText(start, end - start, 1142 &fYankBuffer[strlen(fYankBuffer)]); 1143 } else { 1144 fYankBuffer = (char *)malloc(end - start + 1); 1145 GetText(start, end - start, fYankBuffer); 1146 } 1147 Delete(); 1148 ScrollToSelection(); 1149 } 1150 break; 1151 } 1152 1153 BTextView::KeyDown(key, count); 1154 break; 1155 1156 case 0x10: // ^p goto previous line 1157 if (IsSelectable()) { 1158 raw = B_UP_ARROW; 1159 BTextView::KeyDown(&raw, 1); 1160 } 1161 break; 1162 1163 case 0x19: // ^y yank text 1164 if ((IsEditable()) && (fYankBuffer)) { 1165 Delete(); 1166 Insert(fYankBuffer); 1167 ScrollToSelection(); 1168 } 1169 break; 1170 1171 default: 1172 BTextView::KeyDown(key, count); 1173 } 1174 } 1175 1176 1177 void 1178 TTextView::MakeFocus(bool focus) 1179 { 1180 if (!focus) { 1181 // ToDo: can someone please translate this? Otherwise I will remove it - axeld. 1182 // MakeFocus(false) は、IM も Inactive になり、そのまま確定される。 1183 // しかしこの場合、input_server が B_INPUT_METHOD_EVENT(B_INPUT_METHOD_STOPPED) 1184 // を送ってこないまま矛盾してしまうので、やむを得ずここでつじつまあわせ処理している。 1185 fInputMethodUndoState.active = false; 1186 // fInputMethodUndoBufferに溜まっている最後のデータがK_INSERTEDなら(確定)正規のバッファへ追加 1187 if (fInputMethodUndoBuffer.CountItems() > 0) { 1188 KUndoItem *item = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1); 1189 if (item->History == K_INSERTED) { 1190 fUndoBuffer.MakeNewUndoItem(); 1191 fUndoBuffer.AddUndo(item->RedoText, item->Length, item->Offset, item->History, item->CursorPos); 1192 fUndoBuffer.MakeNewUndoItem(); 1193 } 1194 fInputMethodUndoBuffer.MakeEmpty(); 1195 } 1196 } 1197 BTextView::MakeFocus(focus); 1198 1199 fParent->Focus(focus); 1200 } 1201 1202 1203 void 1204 TTextView::MessageReceived(BMessage *msg) 1205 { 1206 switch (msg->what) { 1207 case B_SIMPLE_DATA: 1208 { 1209 if (fIncoming) 1210 break; 1211 1212 BMessage message(REFS_RECEIVED); 1213 bool isEnclosure = false; 1214 bool inserted = false; 1215 1216 off_t len = 0; 1217 int32 end; 1218 int32 start; 1219 1220 int32 index = 0; 1221 entry_ref ref; 1222 while (msg->FindRef("refs", index++, &ref) == B_OK) { 1223 BFile file(&ref, B_READ_ONLY); 1224 if (file.InitCheck() == B_OK) { 1225 BNodeInfo node(&file); 1226 char type[B_FILE_NAME_LENGTH]; 1227 node.GetType(type); 1228 1229 off_t size = 0; 1230 file.GetSize(&size); 1231 1232 if (!strncasecmp(type, "text/", 5) && size > 0) { 1233 len += size; 1234 char *text = (char *)malloc(size); 1235 if (text == NULL) { 1236 puts("no memory!"); 1237 return; 1238 } 1239 if (file.Read(text, size) < B_OK) { 1240 puts("could not read from file"); 1241 free(text); 1242 continue; 1243 } 1244 if (!inserted) { 1245 GetSelection(&start, &end); 1246 Delete(); 1247 inserted = true; 1248 } 1249 1250 int32 offset = 0; 1251 for (int32 loop = 0; loop < size; loop++) { 1252 if (text[loop] == '\n') { 1253 Insert(&text[offset], loop - offset + 1); 1254 offset = loop + 1; 1255 } else if (text[loop] == '\r') { 1256 text[loop] = '\n'; 1257 Insert(&text[offset], loop - offset + 1); 1258 if ((loop + 1 < size) 1259 && (text[loop + 1] == '\n')) 1260 loop++; 1261 offset = loop + 1; 1262 } 1263 } 1264 free(text); 1265 } else { 1266 isEnclosure = true; 1267 message.AddRef("refs", &ref); 1268 } 1269 } 1270 } 1271 1272 if (index == 1) { 1273 // message doesn't contain any refs - maybe the parent class likes it 1274 BTextView::MessageReceived(msg); 1275 break; 1276 } 1277 1278 if (inserted) 1279 Select(start, start + len); 1280 if (isEnclosure) 1281 Window()->PostMessage(&message, Window()); 1282 break; 1283 } 1284 1285 case M_HEADER: 1286 msg->FindBool("header", &fHeader); 1287 SetText(NULL); 1288 LoadMessage(fMail, false, NULL); 1289 break; 1290 1291 case M_RAW: 1292 StopLoad(); 1293 1294 msg->FindBool("raw", &fRaw); 1295 SetText(NULL); 1296 LoadMessage(fMail, false, NULL); 1297 break; 1298 1299 case M_SELECT: 1300 if (IsSelectable()) 1301 Select(0, TextLength()); 1302 break; 1303 1304 case M_SAVE: 1305 Save(msg); 1306 break; 1307 1308 case B_NODE_MONITOR: 1309 { 1310 int32 opcode; 1311 if (msg->FindInt32("opcode", &opcode) == B_NO_ERROR) { 1312 dev_t device; 1313 if (msg->FindInt32("device", &device) < B_OK) 1314 break; 1315 ino_t inode; 1316 if (msg->FindInt64("node", &inode) < B_OK) 1317 break; 1318 1319 hyper_text *enclosure; 1320 for (int32 index = 0; 1321 (enclosure = (hyper_text *)fEnclosures->ItemAt(index++)) != NULL;) { 1322 if (device == enclosure->node.device 1323 && inode == enclosure->node.node) { 1324 if (opcode == B_ENTRY_REMOVED) { 1325 enclosure->saved = false; 1326 enclosure->have_ref = false; 1327 } else if (opcode == B_ENTRY_MOVED) { 1328 enclosure->ref.device = device; 1329 msg->FindInt64("to directory", &enclosure->ref.directory); 1330 1331 const char *name; 1332 msg->FindString("name", &name); 1333 enclosure->ref.set_name(name); 1334 } 1335 break; 1336 } 1337 } 1338 } 1339 break; 1340 } 1341 1342 // 1343 // Tracker has responded to a BMessage that was dragged out of 1344 // this email message. It has created a file for us, we just have to 1345 // put the stuff in it. 1346 // 1347 case B_COPY_TARGET: 1348 { 1349 BMessage data; 1350 if (msg->FindMessage("be:originator-data", &data) == B_OK) { 1351 entry_ref directory; 1352 const char *name; 1353 hyper_text *enclosure; 1354 1355 if (data.FindPointer("enclosure", (void **)&enclosure) == B_OK 1356 && msg->FindString("name", &name) == B_OK 1357 && msg->FindRef("directory", &directory) == B_OK) { 1358 switch (enclosure->type) { 1359 case TYPE_ENCLOSURE: 1360 case TYPE_BE_ENCLOSURE: 1361 { 1362 // 1363 // Enclosure. Decode the data and write it out. 1364 // 1365 BMessage saveMsg(M_SAVE); 1366 saveMsg.AddString("name", name); 1367 saveMsg.AddRef("directory", &directory); 1368 saveMsg.AddPointer("enclosure", enclosure); 1369 Save(&saveMsg, false); 1370 break; 1371 } 1372 1373 case TYPE_URL: 1374 { 1375 const char *replyType; 1376 if (msg->FindString("be:filetypes", &replyType) != B_OK) 1377 // drag recipient didn't ask for any specific type, 1378 // create a bookmark file as default 1379 replyType = "application/x-vnd.Be-bookmark"; 1380 1381 BDirectory dir(&directory); 1382 BFile file(&dir, name, B_READ_WRITE); 1383 if (file.InitCheck() == B_OK) { 1384 if (strcmp(replyType, "application/x-vnd.Be-bookmark") == 0) { 1385 // we got a request to create a bookmark, stuff 1386 // it with the url attribute 1387 file.WriteAttr("META:url", B_STRING_TYPE, 0, 1388 enclosure->name, strlen(enclosure->name) + 1); 1389 } else if (strcasecmp(replyType, "text/plain") == 0) { 1390 // create a plain text file, stuff it with 1391 // the url as text 1392 file.Write(enclosure->name, strlen(enclosure->name)); 1393 } 1394 1395 BNodeInfo fileInfo(&file); 1396 fileInfo.SetType(replyType); 1397 } 1398 break; 1399 } 1400 1401 case TYPE_MAILTO: 1402 { 1403 // 1404 // Add some attributes to the already created 1405 // person file. Strip out the 'mailto:' if 1406 // possible. 1407 // 1408 char *addrStart = enclosure->name; 1409 while (true) { 1410 if (*addrStart == ':') { 1411 addrStart++; 1412 break; 1413 } 1414 1415 if (*addrStart == '\0') { 1416 addrStart = enclosure->name; 1417 break; 1418 } 1419 1420 addrStart++; 1421 } 1422 1423 const char *replyType; 1424 if (msg->FindString("be:filetypes", &replyType) != B_OK) 1425 // drag recipient didn't ask for any specific type, 1426 // create a bookmark file as default 1427 replyType = "application/x-vnd.Be-bookmark"; 1428 1429 BDirectory dir(&directory); 1430 BFile file(&dir, name, B_READ_WRITE); 1431 if (file.InitCheck() == B_OK) { 1432 if (!strcmp(replyType, "application/x-person")) { 1433 // we got a request to create a bookmark, stuff 1434 // it with the address attribute 1435 file.WriteAttr("META:email", B_STRING_TYPE, 0, 1436 addrStart, strlen(enclosure->name) + 1); 1437 } else if (!strcasecmp(replyType, "text/plain")) { 1438 // create a plain text file, stuff it with the 1439 // email as text 1440 file.Write(addrStart, strlen(addrStart)); 1441 } 1442 1443 BNodeInfo fileInfo(&file); 1444 fileInfo.SetType(replyType); 1445 } 1446 break; 1447 } 1448 } 1449 } else { 1450 // 1451 // Assume this is handled by BTextView... 1452 // (Probably drag clipping.) 1453 // 1454 BTextView::MessageReceived(msg); 1455 } 1456 } 1457 break; 1458 } 1459 1460 case B_INPUT_METHOD_EVENT: 1461 { 1462 int32 im_op; 1463 if (msg->FindInt32("be:opcode", &im_op) == B_OK){ 1464 switch (im_op) { 1465 case B_INPUT_METHOD_STARTED: 1466 fInputMethodUndoState.replace = true; 1467 fInputMethodUndoState.active = true; 1468 break; 1469 case B_INPUT_METHOD_STOPPED: 1470 fInputMethodUndoState.active = false; 1471 if (fInputMethodUndoBuffer.CountItems() > 0) { 1472 KUndoItem *undo = fInputMethodUndoBuffer.ItemAt( 1473 fInputMethodUndoBuffer.CountItems() - 1); 1474 if (undo->History == K_INSERTED){ 1475 fUndoBuffer.MakeNewUndoItem(); 1476 fUndoBuffer.AddUndo(undo->RedoText, undo->Length, 1477 undo->Offset, undo->History, undo->CursorPos); 1478 fUndoBuffer.MakeNewUndoItem(); 1479 } 1480 fInputMethodUndoBuffer.MakeEmpty(); 1481 } 1482 break; 1483 case B_INPUT_METHOD_CHANGED: 1484 fInputMethodUndoState.active = true; 1485 break; 1486 case B_INPUT_METHOD_LOCATION_REQUEST: 1487 fInputMethodUndoState.active = true; 1488 break; 1489 } 1490 } 1491 BTextView::MessageReceived(msg); 1492 break; 1493 } 1494 1495 case M_REDO: 1496 Redo(); 1497 break; 1498 1499 default: 1500 BTextView::MessageReceived(msg); 1501 } 1502 } 1503 1504 1505 void 1506 TTextView::MouseDown(BPoint where) 1507 { 1508 if (IsEditable()) { 1509 BPoint point; 1510 uint32 buttons; 1511 GetMouse(&point, &buttons); 1512 if (gDictCount && (buttons == B_SECONDARY_MOUSE_BUTTON)) { 1513 int32 offset, start, end, length; 1514 const char *text = Text(); 1515 offset = OffsetAt(where); 1516 if (isalpha(text[offset])) { 1517 length = TextLength(); 1518 1519 //Find start and end of word 1520 //FindSpellBoundry(length, offset, &start, &end); 1521 1522 char c; 1523 bool isAlpha, isApost, isCap; 1524 int32 first; 1525 1526 for (first = offset; 1527 (first >= 0) && (((c = text[first]) == '\'') || isalpha(c)); 1528 first--) {} 1529 isCap = isupper(text[++first]); 1530 1531 for (start = offset, c = text[start], isAlpha = isalpha(c), isApost = (c=='\''); 1532 (start >= 0) && (isAlpha || (isApost 1533 && (((c = text[start+1]) != 's') || !isCap) && isalpha(c) 1534 && isalpha(text[start-1]))); 1535 start--, c = text[start], isAlpha = isalpha(c), isApost = (c == '\'')) {} 1536 start++; 1537 1538 for (end = offset, c = text[end], isAlpha = isalpha(c), isApost = (c == '\''); 1539 (end < length) && (isAlpha || (isApost 1540 && (((c = text[end + 1]) != 's') || !isCap) && isalpha(c))); 1541 end++, c = text[end], isAlpha = isalpha(c), isApost = (c == '\'')) {} 1542 1543 length = end - start; 1544 BString srcWord; 1545 srcWord.SetTo(text + start, length); 1546 1547 bool foundWord = false; 1548 BList matches; 1549 BString *string; 1550 1551 BMenuItem *menuItem; 1552 BPopUpMenu menu("Words", false, false); 1553 1554 for (int32 i = 0; i < gDictCount; i++) 1555 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 BAlert* alert = new BAlert("", 1919 B_TRANSLATE("There is no installed handler for " 1920 "URL links."), B_TRANSLATE("Sorry")); 1921 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1922 alert->Go(); 1923 } 1924 break; 1925 } 1926 1927 case TYPE_MAILTO: 1928 if (be_roster->Launch(B_MAIL_TYPE, 1, &enclosure->name) < B_OK) { 1929 char *argv[] = {(char *)"Mail", enclosure->name}; 1930 be_app->ArgvReceived(2, argv); 1931 } 1932 break; 1933 1934 case TYPE_ENCLOSURE: 1935 case TYPE_BE_ENCLOSURE: 1936 if (!enclosure->have_ref) { 1937 BPath path; 1938 if (find_directory(B_COMMON_TEMP_DIRECTORY, &path) == B_NO_ERROR) { 1939 BDirectory dir(path.Path()); 1940 if (dir.InitCheck() == B_NO_ERROR) { 1941 char name[B_FILE_NAME_LENGTH]; 1942 char baseName[B_FILE_NAME_LENGTH]; 1943 strcpy(baseName, enclosure->name ? enclosure->name : "enclosure"); 1944 strcpy(name, baseName); 1945 for (int32 index = 0; dir.Contains(name); index++) 1946 sprintf(name, "%s_%ld", baseName, index); 1947 1948 BEntry entry(path.Path()); 1949 entry_ref ref; 1950 entry.GetRef(&ref); 1951 1952 BMessage save(M_SAVE); 1953 save.AddRef("directory", &ref); 1954 save.AddString("name", name); 1955 save.AddPointer("enclosure", enclosure); 1956 if (Save(&save) != B_NO_ERROR) 1957 break; 1958 enclosure->saved = false; 1959 } 1960 } 1961 } 1962 1963 BMessenger tracker("application/x-vnd.Be-TRAK"); 1964 if (tracker.IsValid()) { 1965 BMessage openMsg(B_REFS_RECEIVED); 1966 openMsg.AddRef("refs", &enclosure->ref); 1967 tracker.SendMessage(&openMsg); 1968 } 1969 break; 1970 } 1971 } 1972 1973 1974 status_t 1975 TTextView::Save(BMessage *msg, bool makeNewFile) 1976 { 1977 const char *name; 1978 entry_ref ref; 1979 BFile file; 1980 BPath path; 1981 hyper_text *enclosure; 1982 status_t result = B_NO_ERROR; 1983 char entry_name[B_FILE_NAME_LENGTH]; 1984 1985 msg->FindString("name", &name); 1986 msg->FindRef("directory", &ref); 1987 msg->FindPointer("enclosure", (void **)&enclosure); 1988 1989 BDirectory dir; 1990 dir.SetTo(&ref); 1991 result = dir.InitCheck(); 1992 1993 if (result == B_OK) { 1994 if (makeNewFile) { 1995 // 1996 // Search for the file and delete it if it already exists. 1997 // (It may not, that's ok.) 1998 // 1999 BEntry entry; 2000 if (dir.FindEntry(name, &entry) == B_NO_ERROR) 2001 entry.Remove(); 2002 2003 if ((enclosure->have_ref) && (!enclosure->saved)) { 2004 entry.SetTo(&enclosure->ref); 2005 2006 // 2007 // Added true arg and entry_name so MoveTo clobbers as 2008 // before. This may not be the correct behaviour, but 2009 // it's the preserved behaviour. 2010 // 2011 entry.GetName(entry_name); 2012 result = entry.MoveTo(&dir, entry_name, true); 2013 if (result == B_NO_ERROR) { 2014 entry.Rename(name); 2015 entry.GetRef(&enclosure->ref); 2016 entry.GetNodeRef(&enclosure->node); 2017 enclosure->saved = true; 2018 return result; 2019 } 2020 } 2021 2022 if (result == B_NO_ERROR) { 2023 result = dir.CreateFile(name, &file); 2024 if (result == B_NO_ERROR && enclosure->content_type) { 2025 char type[B_MIME_TYPE_LENGTH]; 2026 2027 if (!strcasecmp(enclosure->content_type, "message/rfc822")) 2028 strcpy(type, "text/x-email"); 2029 else if (!strcasecmp(enclosure->content_type, "message/delivery-status")) 2030 strcpy(type, "text/plain"); 2031 else 2032 strcpy(type, enclosure->content_type); 2033 2034 BNodeInfo info(&file); 2035 info.SetType(type); 2036 } 2037 } 2038 } else { 2039 // 2040 // This file was dragged into the tracker or desktop. The file 2041 // already exists. 2042 // 2043 result = file.SetTo(&dir, name, B_WRITE_ONLY); 2044 } 2045 } 2046 2047 if (enclosure->component == NULL) 2048 result = B_ERROR; 2049 2050 if (result == B_NO_ERROR) { 2051 // 2052 // Write the data 2053 // 2054 enclosure->component->GetDecodedData(&file); 2055 2056 BEntry entry; 2057 dir.FindEntry(name, &entry); 2058 entry.GetRef(&enclosure->ref); 2059 enclosure->have_ref = true; 2060 enclosure->saved = true; 2061 entry.GetPath(&path); 2062 update_mime_info(path.Path(), false, true, 2063 !cistrcmp("application/octet-stream", enclosure->content_type ? enclosure->content_type : B_EMPTY_STRING)); 2064 entry.GetNodeRef(&enclosure->node); 2065 watch_node(&enclosure->node, B_WATCH_NAME, this); 2066 } 2067 2068 if (result != B_NO_ERROR) { 2069 beep(); 2070 BAlert* alert = new BAlert("", B_TRANSLATE("An error occurred trying to save " 2071 "the attachment."), B_TRANSLATE("Sorry")); 2072 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2073 alert->Go(); 2074 } 2075 2076 return result; 2077 } 2078 2079 2080 void 2081 TTextView::StopLoad() 2082 { 2083 Window()->Unlock(); 2084 2085 thread_info info; 2086 if (fThread != 0 && get_thread_info(fThread, &info) == B_NO_ERROR) { 2087 fStopLoading = true; 2088 acquire_sem(fStopSem); 2089 int32 result; 2090 wait_for_thread(fThread, &result); 2091 fThread = 0; 2092 release_sem(fStopSem); 2093 fStopLoading = false; 2094 } 2095 2096 Window()->Lock(); 2097 } 2098 2099 2100 bool 2101 TTextView::IsReaderThreadRunning() 2102 { 2103 if (fThread == 0) 2104 return false; 2105 2106 thread_info info; 2107 for (int i = 5; i > 0; i--, usleep(100000)) 2108 if (get_thread_info(fThread, &info) != B_OK) 2109 return false; 2110 return true; 2111 } 2112 2113 2114 void 2115 TTextView::AddAsContent(BEmailMessage *mail, bool wrap, uint32 charset, mail_encoding encoding) 2116 { 2117 if (mail == NULL) 2118 return; 2119 2120 int32 textLength = TextLength(); 2121 const char *text = Text(); 2122 2123 BTextMailComponent *body = mail->Body(); 2124 if (body == NULL) { 2125 if (mail->SetBody(body = new BTextMailComponent()) < B_OK) 2126 return; 2127 } 2128 body->SetEncoding(encoding, charset); 2129 2130 // Just add the text as a whole if we can, or ... 2131 if (!wrap) { 2132 body->AppendText(text); 2133 return; 2134 } 2135 2136 // ... do word wrapping. 2137 2138 BWindow *window = Window(); 2139 char *saveText = strdup(text); 2140 BRect saveTextRect = TextRect(); 2141 2142 // do this before we start messing with the fonts 2143 // the user will never know... 2144 window->DisableUpdates(); 2145 Hide(); 2146 BScrollBar *vScroller = ScrollBar(B_VERTICAL); 2147 BScrollBar *hScroller = ScrollBar(B_HORIZONTAL); 2148 if (vScroller != NULL) 2149 vScroller->SetTarget((BView *)NULL); 2150 if (hScroller != NULL) 2151 hScroller->SetTarget((BView *)NULL); 2152 2153 // Temporarily set the font to a fixed width font for line wrapping 2154 // calculations. If the font doesn't have as many of the symbols as 2155 // the preferred font, go back to using the user's preferred font. 2156 2157 bool *boolArray; 2158 int missingCharactersFixedWidth = 0; 2159 int missingCharactersPreferredFont = 0; 2160 int32 numberOfCharacters; 2161 2162 numberOfCharacters = BString(text).CountChars(); 2163 if (numberOfCharacters > 0 2164 && (boolArray = (bool *)malloc(sizeof(bool) * numberOfCharacters)) != NULL) { 2165 memset(boolArray, 0, sizeof (bool) * numberOfCharacters); 2166 be_fixed_font->GetHasGlyphs(text, numberOfCharacters, boolArray); 2167 for (int i = 0; i < numberOfCharacters; i++) { 2168 if (!boolArray[i]) 2169 missingCharactersFixedWidth += 1; 2170 } 2171 2172 memset(boolArray, 0, sizeof (bool) * numberOfCharacters); 2173 fFont.GetHasGlyphs(text, numberOfCharacters, boolArray); 2174 for (int i = 0; i < numberOfCharacters; i++) { 2175 if (!boolArray[i]) 2176 missingCharactersPreferredFont += 1; 2177 } 2178 2179 free(boolArray); 2180 } 2181 2182 if (missingCharactersFixedWidth > missingCharactersPreferredFont) 2183 SetFontAndColor(0, textLength, &fFont); 2184 else // All things being equal, the fixed font is better for wrapping. 2185 SetFontAndColor(0, textLength, be_fixed_font); 2186 2187 // calculate a text rect that is 72 columns wide 2188 BRect newTextRect = saveTextRect; 2189 newTextRect.right = newTextRect.left + be_fixed_font->StringWidth("m") * 72; 2190 SetTextRect(newTextRect); 2191 2192 // hard-wrap, based on TextView's soft-wrapping 2193 int32 numLines = CountLines(); 2194 bool spaceMoved = false; 2195 char *content = (char *)malloc(textLength + numLines * 72); // more we'll ever need 2196 if (content != NULL) { 2197 int32 contentLength = 0; 2198 2199 for (int32 i = 0; i < numLines; i++) { 2200 int32 startOffset = OffsetAt(i); 2201 if (spaceMoved) { 2202 startOffset++; 2203 spaceMoved = false; 2204 } 2205 int32 endOffset = OffsetAt(i + 1); 2206 int32 lineLength = endOffset - startOffset; 2207 2208 // quick hack to not break URLs into several parts 2209 for (int32 pos = startOffset; pos < endOffset; pos++) { 2210 size_t urlLength; 2211 uint8 type = CheckForURL(text + pos, urlLength); 2212 if (type != 0) 2213 pos += urlLength; 2214 2215 if (pos > endOffset) { 2216 // find first break character after the URL 2217 for (; text[pos]; pos++) { 2218 if (isalnum(text[pos]) || isspace(text[pos])) 2219 break; 2220 } 2221 if (text[pos] && isspace(text[pos]) && text[pos] != '\n') 2222 pos++; 2223 2224 endOffset += pos - endOffset; 2225 lineLength = endOffset - startOffset; 2226 2227 // insert a newline (and the same number of quotes) after the 2228 // URL to make sure the rest of the text is properly wrapped 2229 2230 char buffer[64]; 2231 if (text[pos] == '\n') 2232 buffer[0] = '\0'; 2233 else 2234 strcpy(buffer, "\n"); 2235 2236 size_t quoteLength; 2237 CopyQuotes(text + startOffset, lineLength, buffer + strlen(buffer), quoteLength); 2238 2239 Insert(pos, buffer, strlen(buffer)); 2240 numLines = CountLines(); 2241 text = Text(); 2242 i++; 2243 } 2244 } 2245 if (text[endOffset - 1] != ' ' 2246 && text[endOffset - 1] != '\n' 2247 && text[endOffset] == ' ') { 2248 // make sure spaces will be part of this line 2249 endOffset++; 2250 lineLength++; 2251 spaceMoved = true; 2252 } 2253 2254 memcpy(content + contentLength, text + startOffset, lineLength); 2255 contentLength += lineLength; 2256 2257 // add a newline to every line except for the ones 2258 // that already end in newlines, and the last line 2259 if ((text[endOffset - 1] != '\n') && (i < (numLines - 1))) { 2260 content[contentLength++] = '\n'; 2261 2262 // copy quote level of the first line 2263 size_t quoteLength; 2264 CopyQuotes(text + startOffset, lineLength, content + contentLength, quoteLength); 2265 contentLength += quoteLength; 2266 } 2267 } 2268 content[contentLength] = '\0'; 2269 2270 body->AppendText(content); 2271 free(content); 2272 } 2273 2274 // reset the text rect and font 2275 SetTextRect(saveTextRect); 2276 SetText(saveText); 2277 free(saveText); 2278 SetFontAndColor(0, textLength, &fFont); 2279 2280 // should be OK to hook these back up now 2281 if (vScroller != NULL) 2282 vScroller->SetTarget(this); 2283 if (hScroller != NULL) 2284 hScroller->SetTarget(this); 2285 2286 Show(); 2287 window->EnableUpdates(); 2288 } 2289 2290 2291 // #pragma mark - 2292 2293 2294 TTextView::Reader::Reader(bool header, bool raw, bool quote, bool incoming, 2295 bool stripHeader, bool mime, TTextView *view, BEmailMessage *mail, 2296 BList *list, sem_id sem) 2297 : 2298 fHeader(header), 2299 fRaw(raw), 2300 fQuote(quote), 2301 fIncoming(incoming), 2302 fStripHeader(stripHeader), 2303 fMime(mime), 2304 fView(view), 2305 fMail(mail), 2306 fEnclosures(list), 2307 fStopSem(sem) 2308 { 2309 } 2310 2311 2312 bool 2313 TTextView::Reader::ParseMail(BMailContainer *container, 2314 BTextMailComponent *ignore) 2315 { 2316 int32 count = 0; 2317 for (int32 i = 0; i < container->CountComponents(); i++) { 2318 if (fView->fStopLoading) 2319 return false; 2320 2321 BMailComponent *component; 2322 if ((component = container->GetComponent(i)) == NULL) { 2323 if (fView->fStopLoading) 2324 return false; 2325 2326 hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text)); 2327 if (enclosure == NULL) 2328 return false; 2329 2330 memset(enclosure, 0, sizeof(hyper_text)); 2331 2332 enclosure->type = TYPE_ENCLOSURE; 2333 2334 const char *name = "\n<Attachment: could not handle>\n"; 2335 2336 fView->GetSelection(&enclosure->text_start, &enclosure->text_end); 2337 enclosure->text_start++; 2338 enclosure->text_end += strlen(name) - 1; 2339 2340 Insert(name, strlen(name), true); 2341 fEnclosures->AddItem(enclosure); 2342 continue; 2343 } 2344 2345 count++; 2346 if (component == ignore) 2347 continue; 2348 2349 if (component->ComponentType() == B_MAIL_MULTIPART_CONTAINER) { 2350 BMIMEMultipartMailContainer *c = dynamic_cast<BMIMEMultipartMailContainer *>(container->GetComponent(i)); 2351 ASSERT(c != NULL); 2352 2353 if (!ParseMail(c, ignore)) 2354 count--; 2355 } else if (fIncoming) { 2356 hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text)); 2357 if (enclosure == NULL) 2358 return false; 2359 2360 memset(enclosure, 0, sizeof(hyper_text)); 2361 2362 enclosure->type = TYPE_ENCLOSURE; 2363 enclosure->component = component; 2364 2365 BString name; 2366 char fileName[B_FILE_NAME_LENGTH]; 2367 strcpy(fileName, "untitled"); 2368 if (BMailAttachment *attachment = dynamic_cast <BMailAttachment *> (component)) 2369 attachment->FileName(fileName); 2370 2371 BPath path(fileName); 2372 enclosure->name = strdup(path.Leaf()); 2373 2374 BMimeType type; 2375 component->MIMEType(&type); 2376 enclosure->content_type = strdup(type.Type()); 2377 2378 char typeDescription[B_MIME_TYPE_LENGTH]; 2379 if (type.GetShortDescription(typeDescription) != B_OK) 2380 strcpy(typeDescription, type.Type() ? type.Type() : B_EMPTY_STRING); 2381 2382 name = "\n<"; 2383 name.Append(B_TRANSLATE_COMMENT("Enclosure: %name% (Type: %type%)", 2384 "Don't translate the variables %name% and %type%.")); 2385 name.Append(">\n"); 2386 name.ReplaceFirst("%name%", enclosure->name); 2387 name.ReplaceFirst("%type%", typeDescription); 2388 2389 fView->GetSelection(&enclosure->text_start, &enclosure->text_end); 2390 enclosure->text_start++; 2391 enclosure->text_end += strlen(name.String()) - 1; 2392 2393 Insert(name.String(), name.Length(), true); 2394 fEnclosures->AddItem(enclosure); 2395 } 2396 // default: 2397 // { 2398 // PlainTextBodyComponent *body = dynamic_cast<PlainTextBodyComponent *>(container->GetComponent(i)); 2399 // const char *text; 2400 // if (body && (text = body->Text()) != NULL) 2401 // Insert(text, strlen(text), false); 2402 // } 2403 } 2404 return count > 0; 2405 } 2406 2407 2408 bool 2409 TTextView::Reader::Process(const char *data, int32 data_len, bool isHeader) 2410 { 2411 char line[522]; 2412 int32 count = 0; 2413 2414 for (int32 loop = 0; loop < data_len; loop++) { 2415 if (fView->fStopLoading) 2416 return false; 2417 2418 if (fQuote && (!loop || (loop && data[loop - 1] == '\n'))) { 2419 strcpy(&line[count], QUOTE); 2420 count += strlen(QUOTE); 2421 } 2422 if (!fRaw && fIncoming && (loop < data_len - 7)) { 2423 size_t urlLength; 2424 BString url; 2425 uint8 type = CheckForURL(data + loop, urlLength, &url); 2426 2427 if (type) { 2428 if (!Insert(line, count, false, isHeader)) 2429 return false; 2430 count = 0; 2431 2432 hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text)); 2433 if (enclosure == NULL) 2434 return false; 2435 2436 memset(enclosure, 0, sizeof(hyper_text)); 2437 fView->GetSelection(&enclosure->text_start, 2438 &enclosure->text_end); 2439 enclosure->type = type; 2440 enclosure->name = strdup(url.String()); 2441 if (enclosure->name == NULL) { 2442 free(enclosure); 2443 return false; 2444 } 2445 2446 Insert(&data[loop], urlLength, true, isHeader); 2447 enclosure->text_end += urlLength; 2448 loop += urlLength - 1; 2449 2450 fEnclosures->AddItem(enclosure); 2451 continue; 2452 } 2453 } 2454 if (!fRaw && fMime && data[loop] == '=') { 2455 if ((loop) && (loop < data_len - 1) && (data[loop + 1] == '\r')) 2456 loop += 2; 2457 else 2458 line[count++] = data[loop]; 2459 } else if (data[loop] != '\r') 2460 line[count++] = data[loop]; 2461 2462 if (count > 511 || (count && loop == data_len - 1)) { 2463 if (!Insert(line, count, false, isHeader)) 2464 return false; 2465 count = 0; 2466 } 2467 } 2468 return true; 2469 } 2470 2471 2472 bool 2473 TTextView::Reader::Insert(const char *line, int32 count, bool isHyperLink, 2474 bool isHeader) 2475 { 2476 if (!count) 2477 return true; 2478 2479 BFont font(fView->Font()); 2480 TextRunArray style(count / 8 + 8); 2481 2482 if (fView->fColoredQuotes && !isHeader && !isHyperLink) { 2483 FillInQuoteTextRuns(fView, &fQuoteContext, line, count, font, 2484 &style.Array(), style.MaxEntries()); 2485 } else { 2486 text_run_array &array = style.Array(); 2487 array.count = 1; 2488 array.runs[0].offset = 0; 2489 if (isHeader) { 2490 array.runs[0].color = isHyperLink ? kHyperLinkColor : kHeaderColor; 2491 font.SetSize(font.Size() * 0.9); 2492 } else { 2493 array.runs[0].color = isHyperLink 2494 ? kHyperLinkColor : kNormalTextColor; 2495 } 2496 array.runs[0].font = font; 2497 } 2498 2499 if (!fView->Window()->Lock()) 2500 return false; 2501 2502 fView->Insert(fView->TextLength(), line, count, &style.Array()); 2503 2504 fView->Window()->Unlock(); 2505 return true; 2506 } 2507 2508 2509 status_t 2510 TTextView::Reader::Run(void *_this) 2511 { 2512 Reader *reader = (Reader *)_this; 2513 TTextView *view = reader->fView; 2514 char *msg = NULL; 2515 off_t size = 0; 2516 int32 len = 0; 2517 2518 if (!reader->Lock()) 2519 return B_INTERRUPTED; 2520 2521 BFile *file = dynamic_cast<BFile *>(reader->fMail->Data()); 2522 if (file != NULL) { 2523 len = header_len(file); 2524 2525 if (reader->fHeader) 2526 size = len; 2527 if (reader->fRaw || !reader->fMime) 2528 file->GetSize(&size); 2529 2530 if (size != 0 && (msg = (char *)malloc(size)) == NULL) 2531 goto done; 2532 file->Seek(0, 0); 2533 2534 if (msg) 2535 size = file->Read(msg, size); 2536 } 2537 2538 // show the header? 2539 if (reader->fHeader && len) { 2540 // strip all headers except "From", "To", "Reply-To", "Subject", and "Date" 2541 if (reader->fStripHeader) { 2542 const char *header = msg; 2543 char *buffer = NULL; 2544 2545 while (strncmp(header, "\r\n", 2)) { 2546 const char *eol = header; 2547 while ((eol = strstr(eol, "\r\n")) != NULL && isspace(eol[2])) 2548 eol += 2; 2549 if (eol == NULL) 2550 break; 2551 2552 eol += 2; // CR+LF belong to the line 2553 size_t length = eol - header; 2554 2555 buffer = (char *)realloc(buffer, length + 1); 2556 if (buffer == NULL) 2557 goto done; 2558 2559 memcpy(buffer, header, length); 2560 2561 length = rfc2047_to_utf8(&buffer, &length, length); 2562 2563 if (!strncasecmp(header, "Reply-To: ", 10) 2564 || !strncasecmp(header, "To: ", 4) 2565 || !strncasecmp(header, "From: ", 6) 2566 || !strncasecmp(header, "Subject: ", 8) 2567 || !strncasecmp(header, "Date: ", 6)) 2568 reader->Process(buffer, length, true); 2569 2570 header = eol; 2571 } 2572 free(buffer); 2573 reader->Process("\r\n", 2, true); 2574 } 2575 else if (!reader->Process(msg, len, true)) 2576 goto done; 2577 } 2578 2579 if (reader->fRaw) { 2580 if (!reader->Process((const char *)msg + len, size - len)) 2581 goto done; 2582 } else { 2583 //reader->fFile->Seek(0, 0); 2584 //BEmailMessage *mail = new BEmailMessage(reader->fFile); 2585 BEmailMessage *mail = reader->fMail; 2586 2587 // at first, insert the mail body 2588 BTextMailComponent *body = NULL; 2589 if (mail->BodyText() && !view->fStopLoading) { 2590 char *bodyText = const_cast<char *>(mail->BodyText()); 2591 int32 bodyLength = strlen(bodyText); 2592 body = mail->Body(); 2593 bool isHTML = false; 2594 2595 BMimeType type; 2596 if (body->MIMEType(&type) == B_OK && type == "text/html") { 2597 // strip out HTML tags 2598 char *t = bodyText, *a, *end = bodyText + bodyLength; 2599 bodyText = (char *)malloc(bodyLength + 1); 2600 isHTML = true; 2601 2602 // TODO: is it correct to assume that the text is in Latin-1? 2603 // because if it isn't, the code below won't work correctly... 2604 2605 for (a = bodyText; t < end; t++) { 2606 int32 c = *t; 2607 2608 // compact spaces 2609 bool space = false; 2610 while (c && (c == ' ' || c == '\t')) { 2611 c = *(++t); 2612 space = true; 2613 } 2614 if (space) { 2615 c = ' '; 2616 t--; 2617 } else if (FilterHTMLTag(c, &t, end)) // the tag filter 2618 continue; 2619 2620 Unicode2UTF8(c, &a); 2621 } 2622 2623 *a = 0; 2624 bodyLength = strlen(bodyText); 2625 body = NULL; // to add the HTML text as enclosure 2626 } 2627 if (!reader->Process(bodyText, bodyLength)) 2628 goto done; 2629 2630 if (isHTML) 2631 free(bodyText); 2632 } 2633 2634 if (!reader->ParseMail(mail, body)) 2635 goto done; 2636 2637 //reader->fView->fMail = mail; 2638 } 2639 2640 if (!view->fStopLoading && view->Window()->Lock()) { 2641 view->Select(0, 0); 2642 view->MakeSelectable(true); 2643 if (!reader->fIncoming) 2644 view->MakeEditable(true); 2645 2646 view->Window()->Unlock(); 2647 } 2648 2649 done: 2650 // restore the reading position if available 2651 view->Window()->PostMessage(M_READ_POS); 2652 2653 reader->Unlock(); 2654 2655 delete reader; 2656 free(msg); 2657 2658 return B_NO_ERROR; 2659 } 2660 2661 2662 status_t 2663 TTextView::Reader::Unlock() 2664 { 2665 return release_sem(fStopSem); 2666 } 2667 2668 2669 bool 2670 TTextView::Reader::Lock() 2671 { 2672 if (acquire_sem_etc(fStopSem, 1, B_TIMEOUT, 0) != B_NO_ERROR) 2673 return false; 2674 2675 return true; 2676 } 2677 2678 2679 //==================================================================== 2680 // #pragma mark - 2681 2682 2683 TSavePanel::TSavePanel(hyper_text *enclosure, TTextView *view) 2684 : BFilePanel(B_SAVE_PANEL) 2685 { 2686 fEnclosure = enclosure; 2687 fView = view; 2688 if (enclosure->name) 2689 SetSaveText(enclosure->name); 2690 } 2691 2692 2693 void 2694 TSavePanel::SendMessage(const BMessenger * /* messenger */, BMessage *msg) 2695 { 2696 const char *name = NULL; 2697 BMessage save(M_SAVE); 2698 entry_ref ref; 2699 2700 if ((!msg->FindRef("directory", &ref)) && (!msg->FindString("name", &name))) { 2701 save.AddPointer("enclosure", fEnclosure); 2702 save.AddString("name", name); 2703 save.AddRef("directory", &ref); 2704 fView->Window()->PostMessage(&save, fView); 2705 } 2706 } 2707 2708 2709 void 2710 TSavePanel::SetEnclosure(hyper_text *enclosure) 2711 { 2712 fEnclosure = enclosure; 2713 if (enclosure->name) 2714 SetSaveText(enclosure->name); 2715 else 2716 SetSaveText(""); 2717 2718 if (!IsShowing()) 2719 Show(); 2720 Window()->Activate(); 2721 } 2722 2723 2724 //-------------------------------------------------------------------- 2725 // #pragma mark - 2726 2727 2728 void 2729 TTextView::InsertText(const char *insertText, int32 length, int32 offset, 2730 const text_run_array *runs) 2731 { 2732 ContentChanged(); 2733 2734 // Undo function 2735 2736 int32 cursorPos, dummy; 2737 GetSelection(&cursorPos, &dummy); 2738 2739 if (fInputMethodUndoState.active) { 2740 // IMアクティブ時は、一旦別のバッファへ記憶 2741 fInputMethodUndoBuffer.AddUndo(insertText, length, offset, K_INSERTED, cursorPos); 2742 fInputMethodUndoState.replace = false; 2743 } else { 2744 if (fUndoState.replaced) { 2745 fUndoBuffer.AddUndo(insertText, length, offset, K_REPLACED, cursorPos); 2746 } else { 2747 if (length == 1 && insertText[0] == 0x0a) 2748 fUndoBuffer.MakeNewUndoItem(); 2749 2750 fUndoBuffer.AddUndo(insertText, length, offset, K_INSERTED, cursorPos); 2751 2752 if (length == 1 && insertText[0] == 0x0a) 2753 fUndoBuffer.MakeNewUndoItem(); 2754 } 2755 } 2756 2757 fUndoState.replaced = false; 2758 fUndoState.deleted = false; 2759 2760 struct text_runs : text_run_array { text_run _runs[1]; } style; 2761 if (runs == NULL && IsEditable()) { 2762 style.count = 1; 2763 style.runs[0].offset = 0; 2764 style.runs[0].font = fFont; 2765 style.runs[0].color = kNormalTextColor; 2766 runs = &style; 2767 } 2768 2769 BTextView::InsertText(insertText, length, offset, runs); 2770 2771 if (fSpellCheck && IsEditable()) 2772 { 2773 UpdateSpellMarks(offset, length); 2774 2775 rgb_color color; 2776 GetFontAndColor(offset - 1, NULL, &color); 2777 const char *text = Text(); 2778 2779 if (length > 1 2780 || isalpha(text[offset + 1]) 2781 || (!isalpha(text[offset]) && text[offset] != '\'') 2782 || (color.red == kSpellTextColor.red 2783 && color.green == kSpellTextColor.green 2784 && color.blue == kSpellTextColor.blue)) 2785 { 2786 int32 start, end; 2787 FindSpellBoundry(length, offset, &start, &end); 2788 2789 DSPELL(printf("Offset %ld, start %ld, end %ld\n", offset, start, end)); 2790 DSPELL(printf("\t\"%10.10s...\"\n", text + start)); 2791 2792 CheckSpelling(start, end); 2793 } 2794 } 2795 } 2796 2797 2798 void 2799 TTextView::DeleteText(int32 start, int32 finish) 2800 { 2801 ContentChanged(); 2802 2803 // Undo function 2804 int32 cursorPos, dummy; 2805 GetSelection(&cursorPos, &dummy); 2806 if (fInputMethodUndoState.active) { 2807 if (fInputMethodUndoState.replace) { 2808 fUndoBuffer.AddUndo(&Text()[start], finish - start, start, K_DELETED, cursorPos); 2809 fInputMethodUndoState.replace = false; 2810 } else { 2811 fInputMethodUndoBuffer.AddUndo(&Text()[start], finish - start, start, 2812 K_DELETED, cursorPos); 2813 } 2814 } else 2815 fUndoBuffer.AddUndo(&Text()[start], finish - start, start, K_DELETED, cursorPos); 2816 2817 fUndoState.deleted = true; 2818 fUndoState.replaced = true; 2819 2820 BTextView::DeleteText(start, finish); 2821 if (fSpellCheck && IsEditable()) { 2822 UpdateSpellMarks(start, start - finish); 2823 2824 int32 s, e; 2825 FindSpellBoundry(1, start, &s, &e); 2826 CheckSpelling(s, e); 2827 } 2828 } 2829 2830 2831 void 2832 TTextView::ContentChanged(void) 2833 { 2834 BLooper *looper = Looper(); 2835 if (looper == NULL) 2836 return; 2837 2838 BMessage msg(FIELD_CHANGED); 2839 msg.AddInt32("bitmask", FIELD_BODY); 2840 msg.AddPointer("source", this); 2841 looper->PostMessage(&msg); 2842 } 2843 2844 2845 void 2846 TTextView::CheckSpelling(int32 start, int32 end, int32 flags) 2847 { 2848 const char *text = Text(); 2849 const char *next, *endPtr, *word = NULL; 2850 int32 wordLength = 0, wordOffset; 2851 int32 nextHighlight = start; 2852 BString testWord; 2853 bool isCap = false; 2854 bool isAlpha; 2855 bool isApost; 2856 2857 for (next = text + start, endPtr = text + end; next <= endPtr; next++) { 2858 //printf("next=%c\n", *next); 2859 // ToDo: this has to be refined to other languages... 2860 // Alpha signifies the start of a word 2861 isAlpha = isalpha(*next); 2862 isApost = (*next == '\''); 2863 if (!word && isAlpha) { 2864 //printf("Found word start\n"); 2865 word = next; 2866 wordLength++; 2867 isCap = isupper(*word); 2868 } else if (word && (isAlpha || isApost) && !(isApost && !isalpha(next[1])) 2869 && !(isCap && isApost && (next[1] == 's'))) { 2870 // Word continues check 2871 wordLength++; 2872 //printf("Word continues...\n"); 2873 } else if (word) { 2874 // End of word reached 2875 2876 //printf("Word End\n"); 2877 // Don't check single characters 2878 if (wordLength > 1) { 2879 bool isUpper = true; 2880 2881 // Look for all uppercase 2882 for (int32 i = 0; i < wordLength; i++) { 2883 if (word[i] == '\'') 2884 break; 2885 2886 if (islower(word[i])) { 2887 isUpper = false; 2888 break; 2889 } 2890 } 2891 2892 // Don't check all uppercase words 2893 if (!isUpper) { 2894 bool foundMatch = false; 2895 wordOffset = word - text; 2896 testWord.SetTo(word, wordLength); 2897 2898 testWord = testWord.ToLower(); 2899 DSPELL(printf("Testing: \"%s\"\n", testWord.String())); 2900 2901 int32 key = -1; 2902 if (gDictCount) 2903 key = gExactWords[0]->GetKey(testWord.String()); 2904 2905 // Search all dictionaries 2906 for (int32 i = 0; i < gDictCount; i++) { 2907 if (gExactWords[i]->Lookup(key) >= 0) { 2908 foundMatch = true; 2909 break; 2910 } 2911 } 2912 2913 if (!foundMatch) { 2914 if (flags & S_CLEAR_ERRORS) 2915 RemoveSpellMark(nextHighlight, wordOffset); 2916 2917 if (flags & S_SHOW_ERRORS) 2918 AddSpellMark(wordOffset, wordOffset + wordLength); 2919 } else if (flags & S_CLEAR_ERRORS) 2920 RemoveSpellMark(nextHighlight, wordOffset + wordLength); 2921 2922 nextHighlight = wordOffset + wordLength; 2923 } 2924 } 2925 // Reset state to looking for word 2926 word = NULL; 2927 wordLength = 0; 2928 } 2929 } 2930 2931 if (nextHighlight <= end 2932 && (flags & S_CLEAR_ERRORS) != 0 2933 && nextHighlight < TextLength()) 2934 SetFontAndColor(nextHighlight, end, NULL, B_FONT_ALL, &kNormalTextColor); 2935 } 2936 2937 2938 void 2939 TTextView::FindSpellBoundry(int32 length, int32 offset, int32 *_start, int32 *_end) 2940 { 2941 int32 start, end, textLength; 2942 const char *text = Text(); 2943 textLength = TextLength(); 2944 2945 for (start = offset - 1; start >= 0 2946 && (isalpha(text[start]) || text[start] == '\''); start--) {} 2947 2948 start++; 2949 2950 for (end = offset + length; end < textLength 2951 && (isalpha(text[end]) || text[end] == '\''); end++) {} 2952 2953 *_start = start; 2954 *_end = end; 2955 } 2956 2957 2958 TTextView::spell_mark * 2959 TTextView::FindSpellMark(int32 start, int32 end, spell_mark **_previousMark) 2960 { 2961 spell_mark *lastMark = NULL; 2962 2963 for (spell_mark *spellMark = fFirstSpellMark; spellMark; spellMark = spellMark->next) { 2964 if (spellMark->start < end && spellMark->end > start) { 2965 if (_previousMark) 2966 *_previousMark = lastMark; 2967 return spellMark; 2968 } 2969 2970 lastMark = spellMark; 2971 } 2972 return NULL; 2973 } 2974 2975 2976 void 2977 TTextView::UpdateSpellMarks(int32 offset, int32 length) 2978 { 2979 DSPELL(printf("UpdateSpellMarks: offset = %ld, length = %ld\n", offset, length)); 2980 2981 spell_mark *spellMark; 2982 for (spellMark = fFirstSpellMark; spellMark; spellMark = spellMark->next) { 2983 DSPELL(printf("\tfound: %ld - %ld\n", spellMark->start, spellMark->end)); 2984 2985 if (spellMark->end < offset) 2986 continue; 2987 2988 if (spellMark->start > offset) 2989 spellMark->start += length; 2990 2991 spellMark->end += length; 2992 2993 DSPELL(printf("\t-> reset: %ld - %ld\n", spellMark->start, spellMark->end)); 2994 } 2995 } 2996 2997 2998 status_t 2999 TTextView::AddSpellMark(int32 start, int32 end) 3000 { 3001 DSPELL(printf("AddSpellMark: start = %ld, end = %ld\n", start, end)); 3002 3003 // check if there is already a mark for this passage 3004 spell_mark *spellMark = FindSpellMark(start, end); 3005 if (spellMark) { 3006 if (spellMark->start == start && spellMark->end == end) { 3007 DSPELL(printf("\tfound one\n")); 3008 return B_OK; 3009 } 3010 3011 DSPELL(printf("\tremove old one\n")); 3012 RemoveSpellMark(start, end); 3013 } 3014 3015 spellMark = (spell_mark *)malloc(sizeof(spell_mark)); 3016 if (spellMark == NULL) 3017 return B_NO_MEMORY; 3018 3019 spellMark->start = start; 3020 spellMark->end = end; 3021 spellMark->style = RunArray(start, end); 3022 3023 // set the spell marks appearance 3024 BFont font(fFont); 3025 font.SetFace(B_BOLD_FACE | B_ITALIC_FACE); 3026 SetFontAndColor(start, end, &font, B_FONT_ALL, &kSpellTextColor); 3027 3028 // add it to the queue 3029 spellMark->next = fFirstSpellMark; 3030 fFirstSpellMark = spellMark; 3031 3032 return B_OK; 3033 } 3034 3035 3036 bool 3037 TTextView::RemoveSpellMark(int32 start, int32 end) 3038 { 3039 DSPELL(printf("RemoveSpellMark: start = %ld, end = %ld\n", start, end)); 3040 3041 // find spell mark 3042 spell_mark *lastMark = NULL; 3043 spell_mark *spellMark = FindSpellMark(start, end, &lastMark); 3044 if (spellMark == NULL) { 3045 DSPELL(printf("\tnot found!\n")); 3046 return false; 3047 } 3048 3049 DSPELL(printf("\tfound: %ld - %ld\n", spellMark->start, spellMark->end)); 3050 3051 // dequeue the spell mark 3052 if (lastMark) 3053 lastMark->next = spellMark->next; 3054 else 3055 fFirstSpellMark = spellMark->next; 3056 3057 if (spellMark->start < start) 3058 start = spellMark->start; 3059 if (spellMark->end > end) 3060 end = spellMark->end; 3061 3062 // reset old text run array 3063 SetRunArray(start, end, spellMark->style); 3064 3065 free(spellMark->style); 3066 free(spellMark); 3067 3068 return true; 3069 } 3070 3071 3072 void 3073 TTextView::RemoveSpellMarks() 3074 { 3075 spell_mark *spellMark, *nextMark; 3076 3077 for (spellMark = fFirstSpellMark; spellMark; spellMark = nextMark) { 3078 nextMark = spellMark->next; 3079 3080 // reset old text run array 3081 SetRunArray(spellMark->start, spellMark->end, spellMark->style); 3082 3083 free(spellMark->style); 3084 free(spellMark); 3085 } 3086 3087 fFirstSpellMark = NULL; 3088 } 3089 3090 3091 void 3092 TTextView::EnableSpellCheck(bool enable) 3093 { 3094 if (fSpellCheck == enable) 3095 return; 3096 3097 fSpellCheck = enable; 3098 int32 textLength = TextLength(); 3099 if (fSpellCheck) { 3100 // work-around for a bug in the BTextView class 3101 // which causes lots of flicker 3102 int32 start, end; 3103 GetSelection(&start, &end); 3104 if (start != end) 3105 Select(start, start); 3106 3107 CheckSpelling(0, textLength); 3108 3109 if (start != end) 3110 Select(start, end); 3111 } 3112 else 3113 RemoveSpellMarks(); 3114 } 3115 3116 3117 void 3118 TTextView::WindowActivated(bool flag) 3119 { 3120 if (!flag) { 3121 // WindowActivated(false) は、IM も Inactive になり、そのまま確定される。 3122 // しかしこの場合、input_server が B_INPUT_METHOD_EVENT(B_INPUT_METHOD_STOPPED) 3123 // を送ってこないまま矛盾してしまうので、やむを得ずここでつじつまあわせ処理している。 3124 // OpenBeOSで修正されることを願って暫定処置としている。 3125 fInputMethodUndoState.active = false; 3126 // fInputMethodUndoBufferに溜まっている最後のデータがK_INSERTEDなら(確定)正規のバッファへ追加 3127 if (fInputMethodUndoBuffer.CountItems() > 0) { 3128 KUndoItem *item = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1); 3129 if (item->History == K_INSERTED) { 3130 fUndoBuffer.MakeNewUndoItem(); 3131 fUndoBuffer.AddUndo(item->RedoText, item->Length, item->Offset, 3132 item->History, item->CursorPos); 3133 fUndoBuffer.MakeNewUndoItem(); 3134 } 3135 fInputMethodUndoBuffer.MakeEmpty(); 3136 } 3137 } 3138 BTextView::WindowActivated(flag); 3139 } 3140 3141 3142 void 3143 TTextView::AddQuote(int32 start, int32 finish) 3144 { 3145 BRect rect = Bounds(); 3146 3147 int32 lineStart; 3148 GoToLine(CurrentLine()); 3149 GetSelection(&lineStart, &lineStart); 3150 3151 // make sure that we're changing the whole last line, too 3152 int32 lineEnd = finish > lineStart ? finish - 1 : finish; 3153 { 3154 const char *text = Text(); 3155 while (text[lineEnd] && text[lineEnd] != '\n') 3156 lineEnd++; 3157 } 3158 Select(lineStart, lineEnd); 3159 3160 int32 textLength = lineEnd - lineStart; 3161 char *text = (char *)malloc(textLength + 1); 3162 if (text == NULL) 3163 return; 3164 3165 GetText(lineStart, textLength, text); 3166 3167 int32 quoteLength = strlen(QUOTE); 3168 int32 targetLength = 0; 3169 char *target = NULL; 3170 int32 lastLine = 0; 3171 3172 for (int32 index = 0; index < textLength; index++) { 3173 if (text[index] == '\n' || index == textLength - 1) { 3174 // add quote to this line 3175 int32 lineLength = index - lastLine + 1; 3176 3177 target = (char *)realloc(target, targetLength + lineLength + quoteLength); 3178 if (target == NULL) { 3179 // free the old buffer? 3180 free(text); 3181 return; 3182 } 3183 3184 // copy the quote sign 3185 memcpy(&target[targetLength], QUOTE, quoteLength); 3186 targetLength += quoteLength; 3187 3188 // copy the rest of the line 3189 memcpy(&target[targetLength], &text[lastLine], lineLength); 3190 targetLength += lineLength; 3191 3192 lastLine = index + 1; 3193 } 3194 } 3195 3196 // replace with quoted text 3197 free(text); 3198 Delete(); 3199 3200 if (fColoredQuotes) { 3201 const BFont *font = Font(); 3202 TextRunArray style(targetLength / 8 + 8); 3203 3204 FillInQuoteTextRuns(NULL, NULL, target, targetLength, font, 3205 &style.Array(), style.MaxEntries()); 3206 Insert(target, targetLength, &style.Array()); 3207 } else 3208 Insert(target, targetLength); 3209 3210 free(target); 3211 3212 // redo the old selection (compute the new start if necessary) 3213 Select(start + quoteLength, finish + (targetLength - textLength)); 3214 3215 ScrollTo(rect.LeftTop()); 3216 } 3217 3218 3219 void 3220 TTextView::RemoveQuote(int32 start, int32 finish) 3221 { 3222 BRect rect = Bounds(); 3223 3224 GoToLine(CurrentLine()); 3225 int32 lineStart; 3226 GetSelection(&lineStart, &lineStart); 3227 3228 // make sure that we're changing the whole last line, too 3229 int32 lineEnd = finish > lineStart ? finish - 1 : finish; 3230 const char *text = Text(); 3231 while (text[lineEnd] && text[lineEnd] != '\n') 3232 lineEnd++; 3233 3234 Select(lineStart, lineEnd); 3235 3236 int32 length = lineEnd - lineStart; 3237 char *target = (char *)malloc(length + 1); 3238 if (target == NULL) 3239 return; 3240 3241 int32 quoteLength = strlen(QUOTE); 3242 int32 removed = 0; 3243 text += lineStart; 3244 3245 for (int32 index = 0; index < length;) { 3246 // find out the length of the current line 3247 int32 lineLength = 0; 3248 while (index + lineLength < length && text[lineLength] != '\n') 3249 lineLength++; 3250 3251 // include the newline to be part of this line 3252 if (text[lineLength] == '\n' && index + lineLength + 1 < length) 3253 lineLength++; 3254 3255 if (!strncmp(text, QUOTE, quoteLength)) { 3256 // remove quote 3257 length -= quoteLength; 3258 removed += quoteLength; 3259 3260 lineLength -= quoteLength; 3261 text += quoteLength; 3262 } 3263 3264 if (lineLength == 0) { 3265 target[index] = '\0'; 3266 break; 3267 } 3268 3269 memcpy(&target[index], text, lineLength); 3270 3271 text += lineLength; 3272 index += lineLength; 3273 } 3274 3275 if (removed) { 3276 Delete(); 3277 3278 if (fColoredQuotes) { 3279 const BFont *font = Font(); 3280 TextRunArray style(length / 8 + 8); 3281 3282 FillInQuoteTextRuns(NULL, NULL, target, length, font, 3283 &style.Array(), style.MaxEntries()); 3284 Insert(target, length, &style.Array()); 3285 } else 3286 Insert(target, length); 3287 3288 // redo old selection 3289 bool noSelection = start == finish; 3290 3291 if (start > lineStart + quoteLength) 3292 start -= quoteLength; 3293 else 3294 start = lineStart; 3295 3296 if (noSelection) 3297 finish = start; 3298 else 3299 finish -= removed; 3300 } 3301 3302 free(target); 3303 3304 Select(start, finish); 3305 ScrollTo(rect.LeftTop()); 3306 } 3307 3308 3309 void 3310 TTextView::Undo(BClipboard */*clipboard*/) 3311 { 3312 if (fInputMethodUndoState.active) 3313 return; 3314 3315 int32 length, offset, cursorPos; 3316 undo_type history; 3317 char *text; 3318 status_t status; 3319 3320 status = fUndoBuffer.Undo(&text, &length, &offset, &history, &cursorPos); 3321 if (status == B_OK) { 3322 fUndoBuffer.Off(); 3323 3324 switch (history) { 3325 case K_INSERTED: 3326 BTextView::Delete(offset, offset + length); 3327 Select(offset, offset); 3328 break; 3329 3330 case K_DELETED: 3331 BTextView::Insert(offset, text, length); 3332 Select(offset, offset + length); 3333 break; 3334 3335 case K_REPLACED: 3336 BTextView::Delete(offset, offset + length); 3337 status = fUndoBuffer.Undo(&text, &length, &offset, &history, &cursorPos); 3338 if (status == B_OK && history == K_DELETED) { 3339 BTextView::Insert(offset, text, length); 3340 Select(offset, offset + length); 3341 } else { 3342 ::beep(); 3343 BAlert* alert = new BAlert("", 3344 B_TRANSLATE("Inconsistency occurred in the undo/redo " 3345 "buffer."), B_TRANSLATE("OK")); 3346 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3347 alert->Go(); 3348 } 3349 break; 3350 } 3351 ScrollToSelection(); 3352 ContentChanged(); 3353 fUndoBuffer.On(); 3354 } 3355 } 3356 3357 3358 void 3359 TTextView::Redo() 3360 { 3361 if (fInputMethodUndoState.active) 3362 return; 3363 3364 int32 length, offset, cursorPos; 3365 undo_type history; 3366 char *text; 3367 status_t status; 3368 bool replaced; 3369 3370 status = fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced); 3371 if (status == B_OK) { 3372 fUndoBuffer.Off(); 3373 3374 switch (history) { 3375 case K_INSERTED: 3376 BTextView::Insert(offset, text, length); 3377 Select(offset, offset + length); 3378 break; 3379 3380 case K_DELETED: 3381 BTextView::Delete(offset, offset + length); 3382 if (replaced) { 3383 fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced); 3384 BTextView::Insert(offset, text, length); 3385 } 3386 Select(offset, offset + length); 3387 break; 3388 3389 case K_REPLACED: 3390 ::beep(); 3391 BAlert* alert = new BAlert("", 3392 B_TRANSLATE("Inconsistency occurred in the undo/redo " 3393 "buffer."), B_TRANSLATE("OK")); 3394 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3395 alert->Go(); 3396 break; 3397 } 3398 ScrollToSelection(); 3399 ContentChanged(); 3400 fUndoBuffer.On(); 3401 } 3402 } 3403 3404