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