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