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