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