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 (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 for (int32 i = 0; i < gDictCount; i++) 1554 gWords[i]->FindBestMatches(&matches, 1555 srcWord.String()); 1556 1557 if (matches.CountItems()) { 1558 sort_word_list(&matches, srcWord.String()); 1559 for (int32 i = 0; (string = (BString *)matches.ItemAt(i)) != NULL; i++) { 1560 menu.AddItem((menuItem = new BMenuItem(string->String(), NULL))); 1561 if (!strcasecmp(string->String(), srcWord.String())) { 1562 menuItem->SetEnabled(false); 1563 foundWord = true; 1564 } 1565 delete string; 1566 } 1567 } else { 1568 menuItem = new BMenuItem(B_TRANSLATE("No matches"), NULL); 1569 menuItem->SetEnabled(false); 1570 menu.AddItem(menuItem); 1571 } 1572 1573 BMenuItem *addItem = NULL; 1574 if (!foundWord && gUserDict >= 0) { 1575 menu.AddSeparatorItem(); 1576 addItem = new BMenuItem(B_TRANSLATE("Add"), NULL); 1577 menu.AddItem(addItem); 1578 } 1579 1580 point = ConvertToScreen(where); 1581 if ((menuItem = menu.Go(point, false, false)) != NULL) { 1582 if (menuItem == addItem) { 1583 BString newItem(srcWord.String()); 1584 newItem << "\n"; 1585 gWords[gUserDict]->InitIndex(); 1586 gExactWords[gUserDict]->InitIndex(); 1587 gUserDictFile->Write(newItem.String(), newItem.Length()); 1588 gWords[gUserDict]->BuildIndex(); 1589 gExactWords[gUserDict]->BuildIndex(); 1590 1591 if (fSpellCheck) 1592 CheckSpelling(0, TextLength()); 1593 } else { 1594 int32 len = strlen(menuItem->Label()); 1595 Select(start, start); 1596 Delete(start, end); 1597 Insert(start, menuItem->Label(), len); 1598 Select(start+len, start+len); 1599 } 1600 } 1601 } 1602 return; 1603 } else if (fSpellCheck && IsEditable()) { 1604 int32 start, end; 1605 1606 GetSelection(&start, &end); 1607 FindSpellBoundry(1, start, &start, &end); 1608 CheckSpelling(start, end); 1609 } 1610 } else { 1611 // is not editable, look for enclosures/links 1612 1613 int32 clickOffset = OffsetAt(where); 1614 int32 items = fEnclosures->CountItems(); 1615 for (int32 loop = 0; loop < items; loop++) { 1616 hyper_text *enclosure = (hyper_text*) fEnclosures->ItemAt(loop); 1617 if (clickOffset < enclosure->text_start || clickOffset >= enclosure->text_end) 1618 continue; 1619 1620 // 1621 // The user is clicking on this attachment 1622 // 1623 1624 int32 start; 1625 int32 finish; 1626 Select(enclosure->text_start, enclosure->text_end); 1627 GetSelection(&start, &finish); 1628 Window()->UpdateIfNeeded(); 1629 1630 bool drag = false; 1631 bool held = false; 1632 uint32 buttons = 0; 1633 if (Window()->CurrentMessage()) { 1634 Window()->CurrentMessage()->FindInt32("buttons", 1635 (int32 *) &buttons); 1636 } 1637 1638 // 1639 // If this is the primary button, wait to see if the user is going 1640 // to single click, hold, or drag. 1641 // 1642 if (buttons != B_SECONDARY_MOUSE_BUTTON) { 1643 BPoint point = where; 1644 bigtime_t popupDelay; 1645 get_click_speed(&popupDelay); 1646 popupDelay *= 2; 1647 popupDelay += system_time(); 1648 while (buttons && abs((int)(point.x - where.x)) < 4 1649 && abs((int)(point.y - where.y)) < 4 1650 && system_time() < popupDelay) { 1651 snooze(10000); 1652 GetMouse(&point, &buttons); 1653 } 1654 1655 if (system_time() < popupDelay) { 1656 // 1657 // The user either dragged this or released the button. 1658 // check if it was dragged. 1659 // 1660 if (!(abs((int)(point.x - where.x)) < 4 1661 && abs((int)(point.y - where.y)) < 4) && buttons) 1662 drag = true; 1663 } else { 1664 // 1665 // The user held the button down. 1666 // 1667 held = true; 1668 } 1669 } 1670 1671 // 1672 // If the user has right clicked on this menu, 1673 // or held the button down on it for a while, 1674 // pop up a context menu. 1675 // 1676 if (buttons == B_SECONDARY_MOUSE_BUTTON || held) { 1677 // 1678 // Right mouse click... Display a menu 1679 // 1680 BPoint point = where; 1681 ConvertToScreen(&point); 1682 1683 BMenuItem *item; 1684 if ((enclosure->type != TYPE_ENCLOSURE) 1685 && (enclosure->type != TYPE_BE_ENCLOSURE)) 1686 item = fLinkMenu->Go(point, true); 1687 else 1688 item = fEnclosureMenu->Go(point, true); 1689 1690 BMessage *msg; 1691 if (item && (msg = item->Message()) != NULL) { 1692 if (msg->what == M_SAVE) { 1693 if (fPanel) 1694 fPanel->SetEnclosure(enclosure); 1695 else { 1696 fPanel = new TSavePanel(enclosure, this); 1697 fPanel->Window()->Show(); 1698 } 1699 } else if (msg->what == M_COPY) { 1700 // copy link location to clipboard 1701 1702 if (be_clipboard->Lock()) { 1703 be_clipboard->Clear(); 1704 1705 BMessage *clip; 1706 if ((clip = be_clipboard->Data()) != NULL) { 1707 clip->AddData("text/plain", B_MIME_TYPE, 1708 enclosure->name, strlen(enclosure->name)); 1709 be_clipboard->Commit(); 1710 } 1711 be_clipboard->Unlock(); 1712 } 1713 } else 1714 Open(enclosure); 1715 } 1716 } else { 1717 // 1718 // Left button. If the user single clicks, open this link. 1719 // Otherwise, initiate a drag. 1720 // 1721 if (drag) { 1722 BMessage dragMessage(B_SIMPLE_DATA); 1723 dragMessage.AddInt32("be:actions", B_COPY_TARGET); 1724 dragMessage.AddString("be:types", B_FILE_MIME_TYPE); 1725 switch (enclosure->type) { 1726 case TYPE_BE_ENCLOSURE: 1727 case TYPE_ENCLOSURE: 1728 // 1729 // Attachment. The type is specified in the message. 1730 // 1731 dragMessage.AddString("be:types", B_FILE_MIME_TYPE); 1732 dragMessage.AddString("be:filetypes", 1733 enclosure->content_type ? enclosure->content_type : ""); 1734 dragMessage.AddString("be:clip_name", enclosure->name); 1735 break; 1736 1737 case TYPE_URL: 1738 // 1739 // URL. The user can drag it into the tracker to 1740 // create a bookmark file. 1741 // 1742 dragMessage.AddString("be:types", B_FILE_MIME_TYPE); 1743 dragMessage.AddString("be:filetypes", 1744 "application/x-vnd.Be-bookmark"); 1745 dragMessage.AddString("be:filetypes", "text/plain"); 1746 dragMessage.AddString("be:clip_name", "Bookmark"); 1747 1748 dragMessage.AddString("be:url", enclosure->name); 1749 break; 1750 1751 case TYPE_MAILTO: 1752 // 1753 // Mailto address. The user can drag it into the 1754 // tracker to create a people file. 1755 // 1756 dragMessage.AddString("be:types", B_FILE_MIME_TYPE); 1757 dragMessage.AddString("be:filetypes", 1758 "application/x-person"); 1759 dragMessage.AddString("be:filetypes", "text/plain"); 1760 dragMessage.AddString("be:clip_name", "Person"); 1761 1762 dragMessage.AddString("be:email", enclosure->name); 1763 break; 1764 1765 default: 1766 // 1767 // Otherwise it doesn't have a type that I know how 1768 // to save. It won't have any types and if any 1769 // program wants to accept it, more power to them. 1770 // (tracker won't.) 1771 // 1772 dragMessage.AddString("be:clip_name", "Hyperlink"); 1773 } 1774 1775 BMessage data; 1776 data.AddPointer("enclosure", enclosure); 1777 dragMessage.AddMessage("be:originator-data", &data); 1778 1779 BRegion selectRegion; 1780 GetTextRegion(start, finish, &selectRegion); 1781 DragMessage(&dragMessage, selectRegion.Frame(), this); 1782 } else { 1783 // 1784 // User Single clicked on the attachment. Open it. 1785 // 1786 Open(enclosure); 1787 } 1788 } 1789 return; 1790 } 1791 } 1792 BTextView::MouseDown(where); 1793 } 1794 1795 1796 void 1797 TTextView::MouseMoved(BPoint where, uint32 code, const BMessage *msg) 1798 { 1799 int32 start = OffsetAt(where); 1800 1801 for (int32 loop = fEnclosures->CountItems(); loop-- > 0;) { 1802 hyper_text *enclosure = (hyper_text *)fEnclosures->ItemAt(loop); 1803 if ((start >= enclosure->text_start) && (start < enclosure->text_end)) { 1804 if (!fCursor) 1805 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 1806 fCursor = true; 1807 return; 1808 } 1809 } 1810 1811 if (fCursor) { 1812 SetViewCursor(B_CURSOR_I_BEAM); 1813 fCursor = false; 1814 } 1815 1816 BTextView::MouseMoved(where, code, msg); 1817 } 1818 1819 1820 void 1821 TTextView::ClearList() 1822 { 1823 hyper_text *enclosure; 1824 while ((enclosure = (hyper_text *)fEnclosures->FirstItem()) != NULL) { 1825 fEnclosures->RemoveItem(enclosure); 1826 1827 if (enclosure->name) 1828 free(enclosure->name); 1829 if (enclosure->content_type) 1830 free(enclosure->content_type); 1831 if (enclosure->encoding) 1832 free(enclosure->encoding); 1833 if (enclosure->have_ref && !enclosure->saved) { 1834 BEntry entry(&enclosure->ref); 1835 entry.Remove(); 1836 } 1837 1838 watch_node(&enclosure->node, B_STOP_WATCHING, this); 1839 free(enclosure); 1840 } 1841 } 1842 1843 1844 void 1845 TTextView::LoadMessage(BEmailMessage *mail, bool quoteIt, const char *text) 1846 { 1847 StopLoad(); 1848 1849 fMail = mail; 1850 1851 ClearList(); 1852 1853 MakeSelectable(true); 1854 MakeEditable(false); 1855 if (text) 1856 Insert(text, strlen(text)); 1857 1858 //attr_info attrInfo; 1859 TTextView::Reader *reader = new TTextView::Reader(fHeader, fRaw, quoteIt, fIncoming, 1860 text != NULL, true, 1861 // I removed the following, because I absolutely can't imagine why it's 1862 // there (the mail kit should be able to deal with non-compliant mails) 1863 // -- axeld. 1864 // fFile->GetAttrInfo(B_MAIL_ATTR_MIME, &attrInfo) == B_OK, 1865 this, mail, fEnclosures, fStopSem); 1866 1867 resume_thread(fThread = spawn_thread(Reader::Run, "reader", B_NORMAL_PRIORITY, reader)); 1868 } 1869 1870 1871 void 1872 TTextView::Open(hyper_text *enclosure) 1873 { 1874 switch (enclosure->type) { 1875 case TYPE_URL: 1876 { 1877 const struct {const char *urlType, *handler; } handlerTable[] = { 1878 {"http", B_URL_HTTP}, 1879 {"https", B_URL_HTTPS}, 1880 {"ftp", B_URL_FTP}, 1881 {"gopher", B_URL_GOPHER}, 1882 {"mailto", B_URL_MAILTO}, 1883 {"news", B_URL_NEWS}, 1884 {"nntp", B_URL_NNTP}, 1885 {"telnet", B_URL_TELNET}, 1886 {"rlogin", B_URL_RLOGIN}, 1887 {"tn3270", B_URL_TN3270}, 1888 {"wais", B_URL_WAIS}, 1889 {"file", B_URL_FILE}, 1890 {NULL, NULL} 1891 }; 1892 const char *handlerToLaunch = NULL; 1893 1894 const char *colonPos = strchr(enclosure->name, ':'); 1895 if (colonPos) { 1896 int urlTypeLength = colonPos - enclosure->name; 1897 1898 for (int32 index = 0; handlerTable[index].urlType; index++) { 1899 if (!strncasecmp(enclosure->name, 1900 handlerTable[index].urlType, urlTypeLength)) { 1901 handlerToLaunch = handlerTable[index].handler; 1902 break; 1903 } 1904 } 1905 } 1906 if (handlerToLaunch) { 1907 entry_ref appRef; 1908 if (be_roster->FindApp(handlerToLaunch, &appRef) != B_OK) 1909 handlerToLaunch = NULL; 1910 } 1911 if (!handlerToLaunch) 1912 handlerToLaunch = "application/x-vnd.Be-Bookmark"; 1913 1914 status_t result = be_roster->Launch(handlerToLaunch, 1, &enclosure->name); 1915 if (result != B_NO_ERROR && result != B_ALREADY_RUNNING) { 1916 beep(); 1917 (new BAlert("", 1918 B_TRANSLATE("There is no installed handler for " 1919 "URL links."), 1920 "Sorry"))->Go(); 1921 } 1922 break; 1923 } 1924 1925 case TYPE_MAILTO: 1926 if (be_roster->Launch(B_MAIL_TYPE, 1, &enclosure->name) < B_OK) { 1927 char *argv[] = {(char *)"Mail", enclosure->name}; 1928 be_app->ArgvReceived(2, argv); 1929 } 1930 break; 1931 1932 case TYPE_ENCLOSURE: 1933 case TYPE_BE_ENCLOSURE: 1934 if (!enclosure->have_ref) { 1935 BPath path; 1936 if (find_directory(B_COMMON_TEMP_DIRECTORY, &path) == B_NO_ERROR) { 1937 BDirectory dir(path.Path()); 1938 if (dir.InitCheck() == B_NO_ERROR) { 1939 char name[B_FILE_NAME_LENGTH]; 1940 char baseName[B_FILE_NAME_LENGTH]; 1941 strcpy(baseName, enclosure->name ? enclosure->name : "enclosure"); 1942 strcpy(name, baseName); 1943 for (int32 index = 0; dir.Contains(name); index++) 1944 sprintf(name, "%s_%ld", baseName, index); 1945 1946 BEntry entry(path.Path()); 1947 entry_ref ref; 1948 entry.GetRef(&ref); 1949 1950 BMessage save(M_SAVE); 1951 save.AddRef("directory", &ref); 1952 save.AddString("name", name); 1953 save.AddPointer("enclosure", enclosure); 1954 if (Save(&save) != B_NO_ERROR) 1955 break; 1956 enclosure->saved = false; 1957 } 1958 } 1959 } 1960 1961 BMessenger tracker("application/x-vnd.Be-TRAK"); 1962 if (tracker.IsValid()) { 1963 BMessage openMsg(B_REFS_RECEIVED); 1964 openMsg.AddRef("refs", &enclosure->ref); 1965 tracker.SendMessage(&openMsg); 1966 } 1967 break; 1968 } 1969 } 1970 1971 1972 status_t 1973 TTextView::Save(BMessage *msg, bool makeNewFile) 1974 { 1975 const char *name; 1976 entry_ref ref; 1977 BFile file; 1978 BPath path; 1979 hyper_text *enclosure; 1980 status_t result = B_NO_ERROR; 1981 char entry_name[B_FILE_NAME_LENGTH]; 1982 1983 msg->FindString("name", &name); 1984 msg->FindRef("directory", &ref); 1985 msg->FindPointer("enclosure", (void **)&enclosure); 1986 1987 BDirectory dir; 1988 dir.SetTo(&ref); 1989 result = dir.InitCheck(); 1990 1991 if (result == B_OK) { 1992 if (makeNewFile) { 1993 // 1994 // Search for the file and delete it if it already exists. 1995 // (It may not, that's ok.) 1996 // 1997 BEntry entry; 1998 if (dir.FindEntry(name, &entry) == B_NO_ERROR) 1999 entry.Remove(); 2000 2001 if ((enclosure->have_ref) && (!enclosure->saved)) { 2002 entry.SetTo(&enclosure->ref); 2003 2004 // 2005 // Added true arg and entry_name so MoveTo clobbers as 2006 // before. This may not be the correct behaviour, but 2007 // it's the preserved behaviour. 2008 // 2009 entry.GetName(entry_name); 2010 result = entry.MoveTo(&dir, entry_name, true); 2011 if (result == B_NO_ERROR) { 2012 entry.Rename(name); 2013 entry.GetRef(&enclosure->ref); 2014 entry.GetNodeRef(&enclosure->node); 2015 enclosure->saved = true; 2016 return result; 2017 } 2018 } 2019 2020 if (result == B_NO_ERROR) { 2021 result = dir.CreateFile(name, &file); 2022 if (result == B_NO_ERROR && enclosure->content_type) { 2023 char type[B_MIME_TYPE_LENGTH]; 2024 2025 if (!strcasecmp(enclosure->content_type, "message/rfc822")) 2026 strcpy(type, "text/x-email"); 2027 else if (!strcasecmp(enclosure->content_type, "message/delivery-status")) 2028 strcpy(type, "text/plain"); 2029 else 2030 strcpy(type, enclosure->content_type); 2031 2032 BNodeInfo info(&file); 2033 info.SetType(type); 2034 } 2035 } 2036 } else { 2037 // 2038 // This file was dragged into the tracker or desktop. The file 2039 // already exists. 2040 // 2041 result = file.SetTo(&dir, name, B_WRITE_ONLY); 2042 } 2043 } 2044 2045 if (enclosure->component == NULL) 2046 result = B_ERROR; 2047 2048 if (result == B_NO_ERROR) { 2049 // 2050 // Write the data 2051 // 2052 enclosure->component->GetDecodedData(&file); 2053 2054 BEntry entry; 2055 dir.FindEntry(name, &entry); 2056 entry.GetRef(&enclosure->ref); 2057 enclosure->have_ref = true; 2058 enclosure->saved = true; 2059 entry.GetPath(&path); 2060 update_mime_info(path.Path(), false, true, 2061 !cistrcmp("application/octet-stream", enclosure->content_type ? enclosure->content_type : B_EMPTY_STRING)); 2062 entry.GetNodeRef(&enclosure->node); 2063 watch_node(&enclosure->node, B_WATCH_NAME, this); 2064 } 2065 2066 if (result != B_NO_ERROR) { 2067 beep(); 2068 (new BAlert("", B_TRANSLATE("An error occurred trying to save " 2069 "the attachment."), 2070 B_TRANSLATE("Sorry")))->Go(); 2071 } 2072 2073 return result; 2074 } 2075 2076 2077 void 2078 TTextView::StopLoad() 2079 { 2080 Window()->Unlock(); 2081 2082 thread_info info; 2083 if (fThread != 0 && get_thread_info(fThread, &info) == B_NO_ERROR) { 2084 fStopLoading = true; 2085 acquire_sem(fStopSem); 2086 int32 result; 2087 wait_for_thread(fThread, &result); 2088 fThread = 0; 2089 release_sem(fStopSem); 2090 fStopLoading = false; 2091 } 2092 2093 Window()->Lock(); 2094 } 2095 2096 2097 bool 2098 TTextView::IsReaderThreadRunning() 2099 { 2100 if (fThread == 0) 2101 return false; 2102 2103 thread_info info; 2104 for (int i = 5; i > 0; i--, usleep(100000)) 2105 if (get_thread_info(fThread, &info) != B_OK) 2106 return false; 2107 return true; 2108 } 2109 2110 2111 void 2112 TTextView::AddAsContent(BEmailMessage *mail, bool wrap, uint32 charset, mail_encoding encoding) 2113 { 2114 if (mail == NULL) 2115 return; 2116 2117 int32 textLength = TextLength(); 2118 const char *text = Text(); 2119 2120 BTextMailComponent *body = mail->Body(); 2121 if (body == NULL) { 2122 if (mail->SetBody(body = new BTextMailComponent()) < B_OK) 2123 return; 2124 } 2125 body->SetEncoding(encoding, charset); 2126 2127 // Just add the text as a whole if we can, or ... 2128 if (!wrap) { 2129 body->AppendText(text); 2130 return; 2131 } 2132 2133 // ... do word wrapping. 2134 2135 BWindow *window = Window(); 2136 char *saveText = strdup(text); 2137 BRect saveTextRect = TextRect(); 2138 2139 // do this before we start messing with the fonts 2140 // the user will never know... 2141 window->DisableUpdates(); 2142 Hide(); 2143 BScrollBar *vScroller = ScrollBar(B_VERTICAL); 2144 BScrollBar *hScroller = ScrollBar(B_HORIZONTAL); 2145 if (vScroller != NULL) 2146 vScroller->SetTarget((BView *)NULL); 2147 if (hScroller != NULL) 2148 hScroller->SetTarget((BView *)NULL); 2149 2150 // Temporarily set the font to a fixed width font for line wrapping 2151 // calculations. If the font doesn't have as many of the symbols as 2152 // the preferred font, go back to using the user's preferred font. 2153 2154 bool *boolArray; 2155 int missingCharactersFixedWidth = 0; 2156 int missingCharactersPreferredFont = 0; 2157 int32 numberOfCharacters; 2158 2159 numberOfCharacters = BString(text).CountChars(); 2160 if (numberOfCharacters > 0 2161 && (boolArray = (bool *)malloc(sizeof(bool) * numberOfCharacters)) != NULL) { 2162 memset(boolArray, 0, sizeof (bool) * numberOfCharacters); 2163 be_fixed_font->GetHasGlyphs(text, numberOfCharacters, boolArray); 2164 for (int i = 0; i < numberOfCharacters; i++) { 2165 if (!boolArray[i]) 2166 missingCharactersFixedWidth += 1; 2167 } 2168 2169 memset(boolArray, 0, sizeof (bool) * numberOfCharacters); 2170 fFont.GetHasGlyphs(text, numberOfCharacters, boolArray); 2171 for (int i = 0; i < numberOfCharacters; i++) { 2172 if (!boolArray[i]) 2173 missingCharactersPreferredFont += 1; 2174 } 2175 2176 free(boolArray); 2177 } 2178 2179 if (missingCharactersFixedWidth > missingCharactersPreferredFont) 2180 SetFontAndColor(0, textLength, &fFont); 2181 else // All things being equal, the fixed font is better for wrapping. 2182 SetFontAndColor(0, textLength, be_fixed_font); 2183 2184 // calculate a text rect that is 72 columns wide 2185 BRect newTextRect = saveTextRect; 2186 newTextRect.right = newTextRect.left + be_fixed_font->StringWidth("m") * 72; 2187 SetTextRect(newTextRect); 2188 2189 // hard-wrap, based on TextView's soft-wrapping 2190 int32 numLines = CountLines(); 2191 bool spaceMoved = false; 2192 char *content = (char *)malloc(textLength + numLines * 72); // more we'll ever need 2193 if (content != NULL) { 2194 int32 contentLength = 0; 2195 2196 for (int32 i = 0; i < numLines; i++) { 2197 int32 startOffset = OffsetAt(i); 2198 if (spaceMoved) { 2199 startOffset++; 2200 spaceMoved = false; 2201 } 2202 int32 endOffset = OffsetAt(i + 1); 2203 int32 lineLength = endOffset - startOffset; 2204 2205 // quick hack to not break URLs into several parts 2206 for (int32 pos = startOffset; pos < endOffset; pos++) { 2207 size_t urlLength; 2208 uint8 type = CheckForURL(text + pos, urlLength); 2209 if (type != 0) 2210 pos += urlLength; 2211 2212 if (pos > endOffset) { 2213 // find first break character after the URL 2214 for (; text[pos]; pos++) { 2215 if (isalnum(text[pos]) || isspace(text[pos])) 2216 break; 2217 } 2218 if (text[pos] && isspace(text[pos]) && text[pos] != '\n') 2219 pos++; 2220 2221 endOffset += pos - endOffset; 2222 lineLength = endOffset - startOffset; 2223 2224 // insert a newline (and the same number of quotes) after the 2225 // URL to make sure the rest of the text is properly wrapped 2226 2227 char buffer[64]; 2228 if (text[pos] == '\n') 2229 buffer[0] = '\0'; 2230 else 2231 strcpy(buffer, "\n"); 2232 2233 size_t quoteLength; 2234 CopyQuotes(text + startOffset, lineLength, buffer + strlen(buffer), quoteLength); 2235 2236 Insert(pos, buffer, strlen(buffer)); 2237 numLines = CountLines(); 2238 text = Text(); 2239 i++; 2240 } 2241 } 2242 if (text[endOffset - 1] != ' ' 2243 && text[endOffset - 1] != '\n' 2244 && text[endOffset] == ' ') { 2245 // make sure spaces will be part of this line 2246 endOffset++; 2247 lineLength++; 2248 spaceMoved = true; 2249 } 2250 2251 memcpy(content + contentLength, text + startOffset, lineLength); 2252 contentLength += lineLength; 2253 2254 // add a newline to every line except for the ones 2255 // that already end in newlines, and the last line 2256 if ((text[endOffset - 1] != '\n') && (i < (numLines - 1))) { 2257 content[contentLength++] = '\n'; 2258 2259 // copy quote level of the first line 2260 size_t quoteLength; 2261 CopyQuotes(text + startOffset, lineLength, content + contentLength, quoteLength); 2262 contentLength += quoteLength; 2263 } 2264 } 2265 content[contentLength] = '\0'; 2266 2267 body->AppendText(content); 2268 free(content); 2269 } 2270 2271 // reset the text rect and font 2272 SetTextRect(saveTextRect); 2273 SetText(saveText); 2274 free(saveText); 2275 SetFontAndColor(0, textLength, &fFont); 2276 2277 // should be OK to hook these back up now 2278 if (vScroller != NULL) 2279 vScroller->SetTarget(this); 2280 if (hScroller != NULL) 2281 hScroller->SetTarget(this); 2282 2283 Show(); 2284 window->EnableUpdates(); 2285 } 2286 2287 2288 // #pragma mark - 2289 2290 2291 TTextView::Reader::Reader(bool header, bool raw, bool quote, bool incoming, 2292 bool stripHeader, bool mime, TTextView *view, BEmailMessage *mail, 2293 BList *list, sem_id sem) 2294 : 2295 fHeader(header), 2296 fRaw(raw), 2297 fQuote(quote), 2298 fIncoming(incoming), 2299 fStripHeader(stripHeader), 2300 fMime(mime), 2301 fView(view), 2302 fMail(mail), 2303 fEnclosures(list), 2304 fStopSem(sem) 2305 { 2306 } 2307 2308 2309 bool 2310 TTextView::Reader::ParseMail(BMailContainer *container, 2311 BTextMailComponent *ignore) 2312 { 2313 int32 count = 0; 2314 for (int32 i = 0; i < container->CountComponents(); i++) { 2315 if (fView->fStopLoading) 2316 return false; 2317 2318 BMailComponent *component; 2319 if ((component = container->GetComponent(i)) == NULL) { 2320 if (fView->fStopLoading) 2321 return false; 2322 2323 hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text)); 2324 if (enclosure == NULL) 2325 return false; 2326 2327 memset(enclosure, 0, sizeof(hyper_text)); 2328 2329 enclosure->type = TYPE_ENCLOSURE; 2330 2331 const char *name = "\n<Attachment: could not handle>\n"; 2332 2333 fView->GetSelection(&enclosure->text_start, &enclosure->text_end); 2334 enclosure->text_start++; 2335 enclosure->text_end += strlen(name) - 1; 2336 2337 Insert(name, strlen(name), true); 2338 fEnclosures->AddItem(enclosure); 2339 continue; 2340 } 2341 2342 count++; 2343 if (component == ignore) 2344 continue; 2345 2346 if (component->ComponentType() == B_MAIL_MULTIPART_CONTAINER) { 2347 BMIMEMultipartMailContainer *c = dynamic_cast<BMIMEMultipartMailContainer *>(container->GetComponent(i)); 2348 ASSERT(c != NULL); 2349 2350 if (!ParseMail(c, ignore)) 2351 count--; 2352 } else if (fIncoming) { 2353 hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text)); 2354 if (enclosure == NULL) 2355 return false; 2356 2357 memset(enclosure, 0, sizeof(hyper_text)); 2358 2359 enclosure->type = TYPE_ENCLOSURE; 2360 enclosure->component = component; 2361 2362 BString name; 2363 char fileName[B_FILE_NAME_LENGTH]; 2364 strcpy(fileName, "untitled"); 2365 if (BMailAttachment *attachment = dynamic_cast <BMailAttachment *> (component)) 2366 attachment->FileName(fileName); 2367 2368 BPath path(fileName); 2369 enclosure->name = strdup(path.Leaf()); 2370 2371 BMimeType type; 2372 component->MIMEType(&type); 2373 enclosure->content_type = strdup(type.Type()); 2374 2375 char typeDescription[B_MIME_TYPE_LENGTH]; 2376 if (type.GetShortDescription(typeDescription) != B_OK) 2377 strcpy(typeDescription, type.Type() ? type.Type() : B_EMPTY_STRING); 2378 2379 name = "\n<"; 2380 name.Append(B_TRANSLATE_COMMENT("Enclosure: %name% (Type: %type%)", 2381 "Don't translate the variables %name% and %type%.")); 2382 name.Append(">\n"); 2383 name.ReplaceFirst("%name%", enclosure->name); 2384 name.ReplaceFirst("%type%", typeDescription); 2385 2386 fView->GetSelection(&enclosure->text_start, &enclosure->text_end); 2387 enclosure->text_start++; 2388 enclosure->text_end += strlen(name.String()) - 1; 2389 2390 Insert(name.String(), name.Length(), true); 2391 fEnclosures->AddItem(enclosure); 2392 } 2393 // default: 2394 // { 2395 // PlainTextBodyComponent *body = dynamic_cast<PlainTextBodyComponent *>(container->GetComponent(i)); 2396 // const char *text; 2397 // if (body && (text = body->Text()) != NULL) 2398 // Insert(text, strlen(text), false); 2399 // } 2400 } 2401 return count > 0; 2402 } 2403 2404 2405 bool 2406 TTextView::Reader::Process(const char *data, int32 data_len, bool isHeader) 2407 { 2408 char line[522]; 2409 int32 count = 0; 2410 2411 for (int32 loop = 0; loop < data_len; loop++) { 2412 if (fView->fStopLoading) 2413 return false; 2414 2415 if (fQuote && (!loop || (loop && data[loop - 1] == '\n'))) { 2416 strcpy(&line[count], QUOTE); 2417 count += strlen(QUOTE); 2418 } 2419 if (!fRaw && fIncoming && (loop < data_len - 7)) { 2420 size_t urlLength; 2421 BString url; 2422 uint8 type = CheckForURL(data + loop, urlLength, &url); 2423 2424 if (type) { 2425 if (!Insert(line, count, false, isHeader)) 2426 return false; 2427 count = 0; 2428 2429 hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text)); 2430 if (enclosure == NULL) 2431 return false; 2432 2433 memset(enclosure, 0, sizeof(hyper_text)); 2434 fView->GetSelection(&enclosure->text_start, 2435 &enclosure->text_end); 2436 enclosure->type = type; 2437 enclosure->name = strdup(url.String()); 2438 if (enclosure->name == NULL) { 2439 free(enclosure); 2440 return false; 2441 } 2442 2443 Insert(&data[loop], urlLength, true, isHeader); 2444 enclosure->text_end += urlLength; 2445 loop += urlLength - 1; 2446 2447 fEnclosures->AddItem(enclosure); 2448 continue; 2449 } 2450 } 2451 if (!fRaw && fMime && data[loop] == '=') { 2452 if ((loop) && (loop < data_len - 1) && (data[loop + 1] == '\r')) 2453 loop += 2; 2454 else 2455 line[count++] = data[loop]; 2456 } else if (data[loop] != '\r') 2457 line[count++] = data[loop]; 2458 2459 if (count > 511 || (count && loop == data_len - 1)) { 2460 if (!Insert(line, count, false, isHeader)) 2461 return false; 2462 count = 0; 2463 } 2464 } 2465 return true; 2466 } 2467 2468 2469 bool 2470 TTextView::Reader::Insert(const char *line, int32 count, bool isHyperLink, 2471 bool isHeader) 2472 { 2473 if (!count) 2474 return true; 2475 2476 BFont font(fView->Font()); 2477 TextRunArray style(count / 8 + 8); 2478 2479 if (fView->fColoredQuotes && !isHeader && !isHyperLink) { 2480 FillInQuoteTextRuns(fView, &fQuoteContext, line, count, font, 2481 &style.Array(), style.MaxEntries()); 2482 } else { 2483 text_run_array &array = style.Array(); 2484 array.count = 1; 2485 array.runs[0].offset = 0; 2486 if (isHeader) { 2487 array.runs[0].color = isHyperLink ? kHyperLinkColor : kHeaderColor; 2488 font.SetSize(font.Size() * 0.9); 2489 } else { 2490 array.runs[0].color = isHyperLink 2491 ? kHyperLinkColor : kNormalTextColor; 2492 } 2493 array.runs[0].font = font; 2494 } 2495 2496 if (!fView->Window()->Lock()) 2497 return false; 2498 2499 fView->Insert(fView->TextLength(), line, count, &style.Array()); 2500 2501 fView->Window()->Unlock(); 2502 return true; 2503 } 2504 2505 2506 status_t 2507 TTextView::Reader::Run(void *_this) 2508 { 2509 Reader *reader = (Reader *)_this; 2510 TTextView *view = reader->fView; 2511 char *msg = NULL; 2512 off_t size = 0; 2513 int32 len = 0; 2514 2515 if (!reader->Lock()) 2516 return B_INTERRUPTED; 2517 2518 BFile *file = dynamic_cast<BFile *>(reader->fMail->Data()); 2519 if (file != NULL) { 2520 len = header_len(file); 2521 2522 if (reader->fHeader) 2523 size = len; 2524 if (reader->fRaw || !reader->fMime) 2525 file->GetSize(&size); 2526 2527 if (size != 0 && (msg = (char *)malloc(size)) == NULL) 2528 goto done; 2529 file->Seek(0, 0); 2530 2531 if (msg) 2532 size = file->Read(msg, size); 2533 } 2534 2535 // show the header? 2536 if (reader->fHeader && len) { 2537 // strip all headers except "From", "To", "Reply-To", "Subject", and "Date" 2538 if (reader->fStripHeader) { 2539 const char *header = msg; 2540 char *buffer = NULL; 2541 2542 while (strncmp(header, "\r\n", 2)) { 2543 const char *eol = header; 2544 while ((eol = strstr(eol, "\r\n")) != NULL && isspace(eol[2])) 2545 eol += 2; 2546 if (eol == NULL) 2547 break; 2548 2549 eol += 2; // CR+LF belong to the line 2550 size_t length = eol - header; 2551 2552 buffer = (char *)realloc(buffer, length + 1); 2553 if (buffer == NULL) 2554 goto done; 2555 2556 memcpy(buffer, header, length); 2557 2558 length = rfc2047_to_utf8(&buffer, &length, length); 2559 2560 if (!strncasecmp(header, "Reply-To: ", 10) 2561 || !strncasecmp(header, "To: ", 4) 2562 || !strncasecmp(header, "From: ", 6) 2563 || !strncasecmp(header, "Subject: ", 8) 2564 || !strncasecmp(header, "Date: ", 6)) 2565 reader->Process(buffer, length, true); 2566 2567 header = eol; 2568 } 2569 free(buffer); 2570 reader->Process("\r\n", 2, true); 2571 } 2572 else if (!reader->Process(msg, len, true)) 2573 goto done; 2574 } 2575 2576 if (reader->fRaw) { 2577 if (!reader->Process((const char *)msg + len, size - len)) 2578 goto done; 2579 } else { 2580 //reader->fFile->Seek(0, 0); 2581 //BEmailMessage *mail = new BEmailMessage(reader->fFile); 2582 BEmailMessage *mail = reader->fMail; 2583 2584 // at first, insert the mail body 2585 BTextMailComponent *body = NULL; 2586 if (mail->BodyText() && !view->fStopLoading) { 2587 char *bodyText = const_cast<char *>(mail->BodyText()); 2588 int32 bodyLength = strlen(bodyText); 2589 body = mail->Body(); 2590 bool isHTML = false; 2591 2592 BMimeType type; 2593 if (body->MIMEType(&type) == B_OK && type == "text/html") { 2594 // strip out HTML tags 2595 char *t = bodyText, *a, *end = bodyText + bodyLength; 2596 bodyText = (char *)malloc(bodyLength + 1); 2597 isHTML = true; 2598 2599 // TODO: is it correct to assume that the text is in Latin-1? 2600 // because if it isn't, the code below won't work correctly... 2601 2602 for (a = bodyText; t < end; t++) { 2603 int32 c = *t; 2604 2605 // compact spaces 2606 bool space = false; 2607 while (c && (c == ' ' || c == '\t')) { 2608 c = *(++t); 2609 space = true; 2610 } 2611 if (space) { 2612 c = ' '; 2613 t--; 2614 } else if (FilterHTMLTag(c, &t, end)) // the tag filter 2615 continue; 2616 2617 Unicode2UTF8(c, &a); 2618 } 2619 2620 *a = 0; 2621 bodyLength = strlen(bodyText); 2622 body = NULL; // to add the HTML text as enclosure 2623 } 2624 if (!reader->Process(bodyText, bodyLength)) 2625 goto done; 2626 2627 if (isHTML) 2628 free(bodyText); 2629 } 2630 2631 if (!reader->ParseMail(mail, body)) 2632 goto done; 2633 2634 //reader->fView->fMail = mail; 2635 } 2636 2637 if (!view->fStopLoading && view->Window()->Lock()) { 2638 view->Select(0, 0); 2639 view->MakeSelectable(true); 2640 if (!reader->fIncoming) 2641 view->MakeEditable(true); 2642 2643 view->Window()->Unlock(); 2644 } 2645 2646 done: 2647 // restore the reading position if available 2648 view->Window()->PostMessage(M_READ_POS); 2649 2650 reader->Unlock(); 2651 2652 delete reader; 2653 free(msg); 2654 2655 return B_NO_ERROR; 2656 } 2657 2658 2659 status_t 2660 TTextView::Reader::Unlock() 2661 { 2662 return release_sem(fStopSem); 2663 } 2664 2665 2666 bool 2667 TTextView::Reader::Lock() 2668 { 2669 if (acquire_sem_etc(fStopSem, 1, B_TIMEOUT, 0) != B_NO_ERROR) 2670 return false; 2671 2672 return true; 2673 } 2674 2675 2676 //==================================================================== 2677 // #pragma mark - 2678 2679 2680 TSavePanel::TSavePanel(hyper_text *enclosure, TTextView *view) 2681 : BFilePanel(B_SAVE_PANEL) 2682 { 2683 fEnclosure = enclosure; 2684 fView = view; 2685 if (enclosure->name) 2686 SetSaveText(enclosure->name); 2687 } 2688 2689 2690 void 2691 TSavePanel::SendMessage(const BMessenger * /* messenger */, BMessage *msg) 2692 { 2693 const char *name = NULL; 2694 BMessage save(M_SAVE); 2695 entry_ref ref; 2696 2697 if ((!msg->FindRef("directory", &ref)) && (!msg->FindString("name", &name))) { 2698 save.AddPointer("enclosure", fEnclosure); 2699 save.AddString("name", name); 2700 save.AddRef("directory", &ref); 2701 fView->Window()->PostMessage(&save, fView); 2702 } 2703 } 2704 2705 2706 void 2707 TSavePanel::SetEnclosure(hyper_text *enclosure) 2708 { 2709 fEnclosure = enclosure; 2710 if (enclosure->name) 2711 SetSaveText(enclosure->name); 2712 else 2713 SetSaveText(""); 2714 2715 if (!IsShowing()) 2716 Show(); 2717 Window()->Activate(); 2718 } 2719 2720 2721 //-------------------------------------------------------------------- 2722 // #pragma mark - 2723 2724 2725 void 2726 TTextView::InsertText(const char *insertText, int32 length, int32 offset, 2727 const text_run_array *runs) 2728 { 2729 ContentChanged(); 2730 2731 // Undo function 2732 2733 int32 cursorPos, dummy; 2734 GetSelection(&cursorPos, &dummy); 2735 2736 if (fInputMethodUndoState.active) { 2737 // IMアクティブ時は、一旦別のバッファへ記憶 2738 fInputMethodUndoBuffer.AddUndo(insertText, length, offset, K_INSERTED, cursorPos); 2739 fInputMethodUndoState.replace = false; 2740 } else { 2741 if (fUndoState.replaced) { 2742 fUndoBuffer.AddUndo(insertText, length, offset, K_REPLACED, cursorPos); 2743 } else { 2744 if (length == 1 && insertText[0] == 0x0a) 2745 fUndoBuffer.MakeNewUndoItem(); 2746 2747 fUndoBuffer.AddUndo(insertText, length, offset, K_INSERTED, cursorPos); 2748 2749 if (length == 1 && insertText[0] == 0x0a) 2750 fUndoBuffer.MakeNewUndoItem(); 2751 } 2752 } 2753 2754 fUndoState.replaced = false; 2755 fUndoState.deleted = false; 2756 2757 struct text_runs : text_run_array { text_run _runs[1]; } style; 2758 if (runs == NULL && IsEditable()) { 2759 style.count = 1; 2760 style.runs[0].offset = 0; 2761 style.runs[0].font = fFont; 2762 style.runs[0].color = kNormalTextColor; 2763 runs = &style; 2764 } 2765 2766 BTextView::InsertText(insertText, length, offset, runs); 2767 2768 if (fSpellCheck && IsEditable()) 2769 { 2770 UpdateSpellMarks(offset, length); 2771 2772 rgb_color color; 2773 GetFontAndColor(offset - 1, NULL, &color); 2774 const char *text = Text(); 2775 2776 if (length > 1 2777 || isalpha(text[offset + 1]) 2778 || (!isalpha(text[offset]) && text[offset] != '\'') 2779 || (color.red == kSpellTextColor.red 2780 && color.green == kSpellTextColor.green 2781 && color.blue == kSpellTextColor.blue)) 2782 { 2783 int32 start, end; 2784 FindSpellBoundry(length, offset, &start, &end); 2785 2786 DSPELL(printf("Offset %ld, start %ld, end %ld\n", offset, start, end)); 2787 DSPELL(printf("\t\"%10.10s...\"\n", text + start)); 2788 2789 CheckSpelling(start, end); 2790 } 2791 } 2792 } 2793 2794 2795 void 2796 TTextView::DeleteText(int32 start, int32 finish) 2797 { 2798 ContentChanged(); 2799 2800 // Undo function 2801 int32 cursorPos, dummy; 2802 GetSelection(&cursorPos, &dummy); 2803 if (fInputMethodUndoState.active) { 2804 if (fInputMethodUndoState.replace) { 2805 fUndoBuffer.AddUndo(&Text()[start], finish - start, start, K_DELETED, cursorPos); 2806 fInputMethodUndoState.replace = false; 2807 } else { 2808 fInputMethodUndoBuffer.AddUndo(&Text()[start], finish - start, start, 2809 K_DELETED, cursorPos); 2810 } 2811 } else 2812 fUndoBuffer.AddUndo(&Text()[start], finish - start, start, K_DELETED, cursorPos); 2813 2814 fUndoState.deleted = true; 2815 fUndoState.replaced = true; 2816 2817 BTextView::DeleteText(start, finish); 2818 if (fSpellCheck && IsEditable()) { 2819 UpdateSpellMarks(start, start - finish); 2820 2821 int32 s, e; 2822 FindSpellBoundry(1, start, &s, &e); 2823 CheckSpelling(s, e); 2824 } 2825 } 2826 2827 2828 void 2829 TTextView::ContentChanged(void) 2830 { 2831 BLooper *looper = Looper(); 2832 if (looper == NULL) 2833 return; 2834 2835 BMessage msg(FIELD_CHANGED); 2836 msg.AddInt32("bitmask", FIELD_BODY); 2837 msg.AddPointer("source", this); 2838 looper->PostMessage(&msg); 2839 } 2840 2841 2842 void 2843 TTextView::CheckSpelling(int32 start, int32 end, int32 flags) 2844 { 2845 const char *text = Text(); 2846 const char *next, *endPtr, *word = NULL; 2847 int32 wordLength = 0, wordOffset; 2848 int32 nextHighlight = start; 2849 BString testWord; 2850 bool isCap = false; 2851 bool isAlpha; 2852 bool isApost; 2853 2854 for (next = text + start, endPtr = text + end; next <= endPtr; next++) { 2855 //printf("next=%c\n", *next); 2856 // ToDo: this has to be refined to other languages... 2857 // Alpha signifies the start of a word 2858 isAlpha = isalpha(*next); 2859 isApost = (*next == '\''); 2860 if (!word && isAlpha) { 2861 //printf("Found word start\n"); 2862 word = next; 2863 wordLength++; 2864 isCap = isupper(*word); 2865 } else if (word && (isAlpha || isApost) && !(isApost && !isalpha(next[1])) 2866 && !(isCap && isApost && (next[1] == 's'))) { 2867 // Word continues check 2868 wordLength++; 2869 //printf("Word continues...\n"); 2870 } else if (word) { 2871 // End of word reached 2872 2873 //printf("Word End\n"); 2874 // Don't check single characters 2875 if (wordLength > 1) { 2876 bool isUpper = true; 2877 2878 // Look for all uppercase 2879 for (int32 i = 0; i < wordLength; i++) { 2880 if (word[i] == '\'') 2881 break; 2882 2883 if (islower(word[i])) { 2884 isUpper = false; 2885 break; 2886 } 2887 } 2888 2889 // Don't check all uppercase words 2890 if (!isUpper) { 2891 bool foundMatch = false; 2892 wordOffset = word - text; 2893 testWord.SetTo(word, wordLength); 2894 2895 testWord = testWord.ToLower(); 2896 DSPELL(printf("Testing: \"%s\"\n", testWord.String())); 2897 2898 int32 key = -1; 2899 if (gDictCount) 2900 key = gExactWords[0]->GetKey(testWord.String()); 2901 2902 // Search all dictionaries 2903 for (int32 i = 0; i < gDictCount; i++) { 2904 if (gExactWords[i]->Lookup(key) >= 0) { 2905 foundMatch = true; 2906 break; 2907 } 2908 } 2909 2910 if (!foundMatch) { 2911 if (flags & S_CLEAR_ERRORS) 2912 RemoveSpellMark(nextHighlight, wordOffset); 2913 2914 if (flags & S_SHOW_ERRORS) 2915 AddSpellMark(wordOffset, wordOffset + wordLength); 2916 } else if (flags & S_CLEAR_ERRORS) 2917 RemoveSpellMark(nextHighlight, wordOffset + wordLength); 2918 2919 nextHighlight = wordOffset + wordLength; 2920 } 2921 } 2922 // Reset state to looking for word 2923 word = NULL; 2924 wordLength = 0; 2925 } 2926 } 2927 2928 if (nextHighlight <= end 2929 && (flags & S_CLEAR_ERRORS) != 0 2930 && nextHighlight < TextLength()) 2931 SetFontAndColor(nextHighlight, end, NULL, B_FONT_ALL, &kNormalTextColor); 2932 } 2933 2934 2935 void 2936 TTextView::FindSpellBoundry(int32 length, int32 offset, int32 *_start, int32 *_end) 2937 { 2938 int32 start, end, textLength; 2939 const char *text = Text(); 2940 textLength = TextLength(); 2941 2942 for (start = offset - 1; start >= 0 2943 && (isalpha(text[start]) || text[start] == '\''); start--) {} 2944 2945 start++; 2946 2947 for (end = offset + length; end < textLength 2948 && (isalpha(text[end]) || text[end] == '\''); end++) {} 2949 2950 *_start = start; 2951 *_end = end; 2952 } 2953 2954 2955 TTextView::spell_mark * 2956 TTextView::FindSpellMark(int32 start, int32 end, spell_mark **_previousMark) 2957 { 2958 spell_mark *lastMark = NULL; 2959 2960 for (spell_mark *spellMark = fFirstSpellMark; spellMark; spellMark = spellMark->next) { 2961 if (spellMark->start < end && spellMark->end > start) { 2962 if (_previousMark) 2963 *_previousMark = lastMark; 2964 return spellMark; 2965 } 2966 2967 lastMark = spellMark; 2968 } 2969 return NULL; 2970 } 2971 2972 2973 void 2974 TTextView::UpdateSpellMarks(int32 offset, int32 length) 2975 { 2976 DSPELL(printf("UpdateSpellMarks: offset = %ld, length = %ld\n", offset, length)); 2977 2978 spell_mark *spellMark; 2979 for (spellMark = fFirstSpellMark; spellMark; spellMark = spellMark->next) { 2980 DSPELL(printf("\tfound: %ld - %ld\n", spellMark->start, spellMark->end)); 2981 2982 if (spellMark->end < offset) 2983 continue; 2984 2985 if (spellMark->start > offset) 2986 spellMark->start += length; 2987 2988 spellMark->end += length; 2989 2990 DSPELL(printf("\t-> reset: %ld - %ld\n", spellMark->start, spellMark->end)); 2991 } 2992 } 2993 2994 2995 status_t 2996 TTextView::AddSpellMark(int32 start, int32 end) 2997 { 2998 DSPELL(printf("AddSpellMark: start = %ld, end = %ld\n", start, end)); 2999 3000 // check if there is already a mark for this passage 3001 spell_mark *spellMark = FindSpellMark(start, end); 3002 if (spellMark) { 3003 if (spellMark->start == start && spellMark->end == end) { 3004 DSPELL(printf("\tfound one\n")); 3005 return B_OK; 3006 } 3007 3008 DSPELL(printf("\tremove old one\n")); 3009 RemoveSpellMark(start, end); 3010 } 3011 3012 spellMark = (spell_mark *)malloc(sizeof(spell_mark)); 3013 if (spellMark == NULL) 3014 return B_NO_MEMORY; 3015 3016 spellMark->start = start; 3017 spellMark->end = end; 3018 spellMark->style = RunArray(start, end); 3019 3020 // set the spell marks appearance 3021 BFont font(fFont); 3022 font.SetFace(B_BOLD_FACE | B_ITALIC_FACE); 3023 SetFontAndColor(start, end, &font, B_FONT_ALL, &kSpellTextColor); 3024 3025 // add it to the queue 3026 spellMark->next = fFirstSpellMark; 3027 fFirstSpellMark = spellMark; 3028 3029 return B_OK; 3030 } 3031 3032 3033 bool 3034 TTextView::RemoveSpellMark(int32 start, int32 end) 3035 { 3036 DSPELL(printf("RemoveSpellMark: start = %ld, end = %ld\n", start, end)); 3037 3038 // find spell mark 3039 spell_mark *lastMark = NULL; 3040 spell_mark *spellMark = FindSpellMark(start, end, &lastMark); 3041 if (spellMark == NULL) { 3042 DSPELL(printf("\tnot found!\n")); 3043 return false; 3044 } 3045 3046 DSPELL(printf("\tfound: %ld - %ld\n", spellMark->start, spellMark->end)); 3047 3048 // dequeue the spell mark 3049 if (lastMark) 3050 lastMark->next = spellMark->next; 3051 else 3052 fFirstSpellMark = spellMark->next; 3053 3054 if (spellMark->start < start) 3055 start = spellMark->start; 3056 if (spellMark->end > end) 3057 end = spellMark->end; 3058 3059 // reset old text run array 3060 SetRunArray(start, end, spellMark->style); 3061 3062 free(spellMark->style); 3063 free(spellMark); 3064 3065 return true; 3066 } 3067 3068 3069 void 3070 TTextView::RemoveSpellMarks() 3071 { 3072 spell_mark *spellMark, *nextMark; 3073 3074 for (spellMark = fFirstSpellMark; spellMark; spellMark = nextMark) { 3075 nextMark = spellMark->next; 3076 3077 // reset old text run array 3078 SetRunArray(spellMark->start, spellMark->end, spellMark->style); 3079 3080 free(spellMark->style); 3081 free(spellMark); 3082 } 3083 3084 fFirstSpellMark = NULL; 3085 } 3086 3087 3088 void 3089 TTextView::EnableSpellCheck(bool enable) 3090 { 3091 if (fSpellCheck == enable) 3092 return; 3093 3094 fSpellCheck = enable; 3095 int32 textLength = TextLength(); 3096 if (fSpellCheck) { 3097 // work-around for a bug in the BTextView class 3098 // which causes lots of flicker 3099 int32 start, end; 3100 GetSelection(&start, &end); 3101 if (start != end) 3102 Select(start, start); 3103 3104 CheckSpelling(0, textLength); 3105 3106 if (start != end) 3107 Select(start, end); 3108 } 3109 else 3110 RemoveSpellMarks(); 3111 } 3112 3113 3114 void 3115 TTextView::WindowActivated(bool flag) 3116 { 3117 if (!flag) { 3118 // WindowActivated(false) は、IM も Inactive になり、そのまま確定される。 3119 // しかしこの場合、input_server が B_INPUT_METHOD_EVENT(B_INPUT_METHOD_STOPPED) 3120 // を送ってこないまま矛盾してしまうので、やむを得ずここでつじつまあわせ処理している。 3121 // OpenBeOSで修正されることを願って暫定処置としている。 3122 fInputMethodUndoState.active = false; 3123 // fInputMethodUndoBufferに溜まっている最後のデータがK_INSERTEDなら(確定)正規のバッファへ追加 3124 if (fInputMethodUndoBuffer.CountItems() > 0) { 3125 KUndoItem *item = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1); 3126 if (item->History == K_INSERTED) { 3127 fUndoBuffer.MakeNewUndoItem(); 3128 fUndoBuffer.AddUndo(item->RedoText, item->Length, item->Offset, 3129 item->History, item->CursorPos); 3130 fUndoBuffer.MakeNewUndoItem(); 3131 } 3132 fInputMethodUndoBuffer.MakeEmpty(); 3133 } 3134 } 3135 BTextView::WindowActivated(flag); 3136 } 3137 3138 3139 void 3140 TTextView::AddQuote(int32 start, int32 finish) 3141 { 3142 BRect rect = Bounds(); 3143 3144 int32 lineStart; 3145 GoToLine(CurrentLine()); 3146 GetSelection(&lineStart, &lineStart); 3147 3148 // make sure that we're changing the whole last line, too 3149 int32 lineEnd = finish > lineStart ? finish - 1 : finish; 3150 { 3151 const char *text = Text(); 3152 while (text[lineEnd] && text[lineEnd] != '\n') 3153 lineEnd++; 3154 } 3155 Select(lineStart, lineEnd); 3156 3157 int32 textLength = lineEnd - lineStart; 3158 char *text = (char *)malloc(textLength + 1); 3159 if (text == NULL) 3160 return; 3161 3162 GetText(lineStart, textLength, text); 3163 3164 int32 quoteLength = strlen(QUOTE); 3165 int32 targetLength = 0; 3166 char *target = NULL; 3167 int32 lastLine = 0; 3168 3169 for (int32 index = 0; index < textLength; index++) { 3170 if (text[index] == '\n' || index == textLength - 1) { 3171 // add quote to this line 3172 int32 lineLength = index - lastLine + 1; 3173 3174 target = (char *)realloc(target, targetLength + lineLength + quoteLength); 3175 if (target == NULL) { 3176 // free the old buffer? 3177 free(text); 3178 return; 3179 } 3180 3181 // copy the quote sign 3182 memcpy(&target[targetLength], QUOTE, quoteLength); 3183 targetLength += quoteLength; 3184 3185 // copy the rest of the line 3186 memcpy(&target[targetLength], &text[lastLine], lineLength); 3187 targetLength += lineLength; 3188 3189 lastLine = index + 1; 3190 } 3191 } 3192 3193 // replace with quoted text 3194 free(text); 3195 Delete(); 3196 3197 if (fColoredQuotes) { 3198 const BFont *font = Font(); 3199 TextRunArray style(targetLength / 8 + 8); 3200 3201 FillInQuoteTextRuns(NULL, NULL, target, targetLength, font, 3202 &style.Array(), style.MaxEntries()); 3203 Insert(target, targetLength, &style.Array()); 3204 } else 3205 Insert(target, targetLength); 3206 3207 free(target); 3208 3209 // redo the old selection (compute the new start if necessary) 3210 Select(start + quoteLength, finish + (targetLength - textLength)); 3211 3212 ScrollTo(rect.LeftTop()); 3213 } 3214 3215 3216 void 3217 TTextView::RemoveQuote(int32 start, int32 finish) 3218 { 3219 BRect rect = Bounds(); 3220 3221 GoToLine(CurrentLine()); 3222 int32 lineStart; 3223 GetSelection(&lineStart, &lineStart); 3224 3225 // make sure that we're changing the whole last line, too 3226 int32 lineEnd = finish > lineStart ? finish - 1 : finish; 3227 const char *text = Text(); 3228 while (text[lineEnd] && text[lineEnd] != '\n') 3229 lineEnd++; 3230 3231 Select(lineStart, lineEnd); 3232 3233 int32 length = lineEnd - lineStart; 3234 char *target = (char *)malloc(length + 1); 3235 if (target == NULL) 3236 return; 3237 3238 int32 quoteLength = strlen(QUOTE); 3239 int32 removed = 0; 3240 text += lineStart; 3241 3242 for (int32 index = 0; index < length;) { 3243 // find out the length of the current line 3244 int32 lineLength = 0; 3245 while (index + lineLength < length && text[lineLength] != '\n') 3246 lineLength++; 3247 3248 // include the newline to be part of this line 3249 if (text[lineLength] == '\n' && index + lineLength + 1 < length) 3250 lineLength++; 3251 3252 if (!strncmp(text, QUOTE, quoteLength)) { 3253 // remove quote 3254 length -= quoteLength; 3255 removed += quoteLength; 3256 3257 lineLength -= quoteLength; 3258 text += quoteLength; 3259 } 3260 3261 if (lineLength == 0) { 3262 target[index] = '\0'; 3263 break; 3264 } 3265 3266 memcpy(&target[index], text, lineLength); 3267 3268 text += lineLength; 3269 index += lineLength; 3270 } 3271 3272 if (removed) { 3273 Delete(); 3274 3275 if (fColoredQuotes) { 3276 const BFont *font = Font(); 3277 TextRunArray style(length / 8 + 8); 3278 3279 FillInQuoteTextRuns(NULL, NULL, target, length, font, 3280 &style.Array(), style.MaxEntries()); 3281 Insert(target, length, &style.Array()); 3282 } else 3283 Insert(target, length); 3284 3285 // redo old selection 3286 bool noSelection = start == finish; 3287 3288 if (start > lineStart + quoteLength) 3289 start -= quoteLength; 3290 else 3291 start = lineStart; 3292 3293 if (noSelection) 3294 finish = start; 3295 else 3296 finish -= removed; 3297 } 3298 3299 free(target); 3300 3301 Select(start, finish); 3302 ScrollTo(rect.LeftTop()); 3303 } 3304 3305 3306 void 3307 TTextView::Undo(BClipboard */*clipboard*/) 3308 { 3309 if (fInputMethodUndoState.active) 3310 return; 3311 3312 int32 length, offset, cursorPos; 3313 undo_type history; 3314 char *text; 3315 status_t status; 3316 3317 status = fUndoBuffer.Undo(&text, &length, &offset, &history, &cursorPos); 3318 if (status == B_OK) { 3319 fUndoBuffer.Off(); 3320 3321 switch (history) { 3322 case K_INSERTED: 3323 BTextView::Delete(offset, offset + length); 3324 Select(offset, offset); 3325 break; 3326 3327 case K_DELETED: 3328 BTextView::Insert(offset, text, length); 3329 Select(offset, offset + length); 3330 break; 3331 3332 case K_REPLACED: 3333 BTextView::Delete(offset, offset + length); 3334 status = fUndoBuffer.Undo(&text, &length, &offset, &history, &cursorPos); 3335 if (status == B_OK && history == K_DELETED) { 3336 BTextView::Insert(offset, text, length); 3337 Select(offset, offset + length); 3338 } else { 3339 ::beep(); 3340 (new BAlert("", 3341 B_TRANSLATE("Inconsistency occurred in the undo/redo " 3342 "buffer."), 3343 B_TRANSLATE("OK")))->Go(); 3344 } 3345 break; 3346 } 3347 ScrollToSelection(); 3348 ContentChanged(); 3349 fUndoBuffer.On(); 3350 } 3351 } 3352 3353 3354 void 3355 TTextView::Redo() 3356 { 3357 if (fInputMethodUndoState.active) 3358 return; 3359 3360 int32 length, offset, cursorPos; 3361 undo_type history; 3362 char *text; 3363 status_t status; 3364 bool replaced; 3365 3366 status = fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced); 3367 if (status == B_OK) { 3368 fUndoBuffer.Off(); 3369 3370 switch (history) { 3371 case K_INSERTED: 3372 BTextView::Insert(offset, text, length); 3373 Select(offset, offset + length); 3374 break; 3375 3376 case K_DELETED: 3377 BTextView::Delete(offset, offset + length); 3378 if (replaced) { 3379 fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced); 3380 BTextView::Insert(offset, text, length); 3381 } 3382 Select(offset, offset + length); 3383 break; 3384 3385 case K_REPLACED: 3386 ::beep(); 3387 (new BAlert("", 3388 B_TRANSLATE("Inconsistency occurred in the undo/redo " 3389 "buffer."), 3390 B_TRANSLATE("OK")))->Go(); 3391 break; 3392 } 3393 ScrollToSelection(); 3394 ContentChanged(); 3395 fUndoBuffer.On(); 3396 } 3397 } 3398 3399