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