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 CONNECTION 23 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 registered trademarks 30 of Be Incorporated in the United States and other countries. Other brand product 31 names are registered trademarks or trademarks of their respective holders. 32 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 TR("An error occurred trying to open this signature."), 746 TR("Sorry")))->Go(); 747 } 748 break; 749 } 750 751 case M_FIND: 752 FindString(msg->FindString("findthis")); 753 break; 754 755 default: 756 BView::MessageReceived(msg); 757 } 758 } 759 760 761 void 762 TContentView::FindString(const char *str) 763 { 764 int32 finish; 765 int32 pass = 0; 766 int32 start = 0; 767 768 if (str == NULL) 769 return; 770 771 // 772 // Start from current selection or from the beginning of the pool 773 // 774 const char *text = fTextView->Text(); 775 int32 count = fTextView->TextLength(); 776 fTextView->GetSelection(&start, &finish); 777 if (start != finish) 778 start = finish; 779 if (!count || text == NULL) 780 return; 781 782 // 783 // Do the find 784 // 785 while (pass < 2) { 786 long found = -1; 787 char lc = tolower(str[0]); 788 char uc = toupper(str[0]); 789 for (long i = start; i < count; i++) { 790 if (text[i] == lc || text[i] == uc) { 791 const char *s = str; 792 const char *t = text + i; 793 while (*s && (tolower(*s) == tolower(*t))) { 794 s++; 795 t++; 796 } 797 if (*s == 0) { 798 found = i; 799 break; 800 } 801 } 802 } 803 804 // 805 // Select the text if it worked 806 // 807 if (found != -1) { 808 Window()->Activate(); 809 fTextView->Select(found, found + strlen(str)); 810 fTextView->ScrollToSelection(); 811 fTextView->MakeFocus(true); 812 return; 813 } 814 else if (start) { 815 start = 0; 816 text = fTextView->Text(); 817 count = fTextView->TextLength(); 818 pass++; 819 } else { 820 beep(); 821 return; 822 } 823 } 824 } 825 826 827 void 828 TContentView::Focus(bool focus) 829 { 830 if (fFocus != focus) { 831 fFocus = focus; 832 Draw(Frame()); 833 } 834 } 835 836 837 void 838 TContentView::FrameResized(float /* width */, float /* height */) 839 { 840 BRect r(fTextView->Bounds()); 841 r.OffsetTo(0, 0); 842 r.InsetBy(5, 5); 843 fTextView->SetTextRect(r); 844 } 845 846 847 //==================================================================== 848 // #pragma mark - 849 850 851 TTextView::TTextView(BRect frame, BRect text, bool incoming, 852 BEmailMessage *mail, TContentView *view, BFont *font, 853 bool showHeader, bool coloredQuotes) 854 : BTextView(frame, "", text, B_FOLLOW_ALL, B_WILL_DRAW | B_NAVIGABLE), 855 fHeader(showHeader), 856 fColoredQuotes(coloredQuotes), 857 fReady(false), 858 fYankBuffer(NULL), 859 fLastPosition(-1), 860 fMail(mail), 861 fFont(font), 862 fParent(view), 863 fStopLoading(false), 864 fThread(0), 865 fPanel(NULL), 866 fIncoming(incoming), 867 fSpellCheck(false), 868 fRaw(false), 869 fCursor(false), 870 fFirstSpellMark(NULL) 871 { 872 BFont menuFont = *be_plain_font; 873 menuFont.SetSize(10); 874 875 fStopSem = create_sem(1, "reader_sem"); 876 SetStylable(true); 877 878 fEnclosures = new BList(); 879 880 // 881 // Enclosure pop up menu 882 // 883 fEnclosureMenu = new BPopUpMenu("Enclosure", false, false); 884 fEnclosureMenu->SetFont(&menuFont); 885 fEnclosureMenu->AddItem(new BMenuItem(TR("Save attachment" B_UTF8_ELLIPSIS), 886 new BMessage(M_SAVE))); 887 fEnclosureMenu->AddItem(new BMenuItem(TR("Open attachment"), 888 new BMessage(M_OPEN))); 889 890 // 891 // Hyperlink pop up menu 892 // 893 fLinkMenu = new BPopUpMenu("Link", false, false); 894 fLinkMenu->SetFont(&menuFont); 895 fLinkMenu->AddItem(new BMenuItem(TR("Open this link"), 896 new BMessage(M_OPEN))); 897 fLinkMenu->AddItem(new BMenuItem(TR("Copy link location"), 898 new BMessage(M_COPY))); 899 900 SetDoesUndo(true); 901 902 //Undo function 903 fUndoBuffer.On(); 904 fInputMethodUndoBuffer.On(); 905 fUndoState.replaced = false; 906 fUndoState.deleted = false; 907 fInputMethodUndoState.active = false; 908 fInputMethodUndoState.replace = false; 909 } 910 911 912 TTextView::~TTextView() 913 { 914 ClearList(); 915 delete fPanel; 916 917 if (fYankBuffer) 918 free(fYankBuffer); 919 920 delete_sem(fStopSem); 921 } 922 923 924 void 925 TTextView::AttachedToWindow() 926 { 927 BTextView::AttachedToWindow(); 928 fFont.SetSpacing(B_FIXED_SPACING); 929 SetFontAndColor(&fFont); 930 931 if (fMail != NULL) { 932 LoadMessage(fMail, false, NULL); 933 if (fIncoming) 934 MakeEditable(false); 935 } 936 } 937 938 939 void 940 TTextView::KeyDown(const char *key, int32 count) 941 { 942 char raw; 943 int32 end; 944 int32 start; 945 uint32 mods; 946 BMessage *msg; 947 int32 textLen = TextLength(); 948 949 msg = Window()->CurrentMessage(); 950 mods = msg->FindInt32("modifiers"); 951 952 switch (key[0]) 953 { 954 case B_HOME: 955 if (IsSelectable()) 956 { 957 if (IsEditable()) 958 BTextView::KeyDown(key, count); 959 else 960 { 961 // scroll to the beginning 962 Select(0, 0); 963 ScrollToSelection(); 964 } 965 } 966 break; 967 968 case B_END: 969 if (IsSelectable()) 970 { 971 if (IsEditable()) 972 BTextView::KeyDown(key, count); 973 else 974 { 975 // scroll to the end 976 int32 length = TextLength(); 977 Select(length, length); 978 ScrollToSelection(); 979 } 980 } 981 break; 982 983 case 0x02: // ^b - back 1 char 984 if (IsSelectable()) 985 { 986 GetSelection(&start, &end); 987 while (!IsInitialUTF8Byte(ByteAt(--start))) 988 { 989 if (start < 0) 990 { 991 start = 0; 992 break; 993 } 994 } 995 if (start >= 0) 996 { 997 Select(start, start); 998 ScrollToSelection(); 999 } 1000 } 1001 break; 1002 1003 case B_DELETE: 1004 if (IsSelectable()) 1005 { 1006 if ((key[0] == B_DELETE) || (mods & B_CONTROL_KEY)) // ^d 1007 { 1008 if (IsEditable()) 1009 { 1010 GetSelection(&start, &end); 1011 if (start != end) 1012 Delete(); 1013 else 1014 { 1015 for (end = start + 1; !IsInitialUTF8Byte(ByteAt(end)); end++) 1016 { 1017 if (end > textLen) 1018 { 1019 end = textLen; 1020 break; 1021 } 1022 } 1023 Select(start, end); 1024 Delete(); 1025 } 1026 } 1027 } 1028 else 1029 Select(textLen, textLen); 1030 ScrollToSelection(); 1031 } 1032 break; 1033 1034 case 0x05: // ^e - end of line 1035 if ((IsSelectable()) && (mods & B_CONTROL_KEY)) 1036 { 1037 if (CurrentLine() == CountLines() - 1) 1038 Select(TextLength(), TextLength()); 1039 else 1040 { 1041 GoToLine(CurrentLine() + 1); 1042 GetSelection(&start, &end); 1043 Select(start - 1, start - 1); 1044 } 1045 } 1046 break; 1047 1048 case 0x06: // ^f - forward 1 char 1049 if (IsSelectable()) 1050 { 1051 GetSelection(&start, &end); 1052 if (end > start) 1053 start = end; 1054 else 1055 { 1056 for (end = start + 1; !IsInitialUTF8Byte(ByteAt(end)); end++) 1057 { 1058 if (end > textLen) 1059 { 1060 end = textLen; 1061 break; 1062 } 1063 } 1064 start = end; 1065 } 1066 Select(start, start); 1067 ScrollToSelection(); 1068 } 1069 break; 1070 1071 case 0x0e: // ^n - next line 1072 if (IsSelectable()) 1073 { 1074 raw = B_DOWN_ARROW; 1075 BTextView::KeyDown(&raw, 1); 1076 } 1077 break; 1078 1079 case 0x0f: // ^o - open line 1080 if (IsEditable()) 1081 { 1082 GetSelection(&start, &end); 1083 Delete(); 1084 1085 char newLine = '\n'; 1086 Insert(&newLine, 1); 1087 Select(start, start); 1088 ScrollToSelection(); 1089 } 1090 break; 1091 1092 case B_PAGE_UP: 1093 if (mods & B_CONTROL_KEY) { // ^k kill text from cursor to e-o-line 1094 if (IsEditable()) { 1095 GetSelection(&start, &end); 1096 if ((start != fLastPosition) && (fYankBuffer)) { 1097 free(fYankBuffer); 1098 fYankBuffer = NULL; 1099 } 1100 fLastPosition = start; 1101 if (CurrentLine() < (CountLines() - 1)) { 1102 GoToLine(CurrentLine() + 1); 1103 GetSelection(&end, &end); 1104 end--; 1105 } 1106 else 1107 end = TextLength(); 1108 if (end < start) 1109 break; 1110 if (start == end) 1111 end++; 1112 Select(start, end); 1113 if (fYankBuffer) { 1114 fYankBuffer = (char *)realloc(fYankBuffer, 1115 strlen(fYankBuffer) + (end - start) + 1); 1116 GetText(start, end - start, 1117 &fYankBuffer[strlen(fYankBuffer)]); 1118 } else { 1119 fYankBuffer = (char *)malloc(end - start + 1); 1120 GetText(start, end - start, fYankBuffer); 1121 } 1122 Delete(); 1123 ScrollToSelection(); 1124 } 1125 break; 1126 } 1127 1128 BTextView::KeyDown(key, count); 1129 break; 1130 1131 case 0x10: // ^p goto previous line 1132 if (IsSelectable()) { 1133 raw = B_UP_ARROW; 1134 BTextView::KeyDown(&raw, 1); 1135 } 1136 break; 1137 1138 case 0x19: // ^y yank text 1139 if ((IsEditable()) && (fYankBuffer)) { 1140 Delete(); 1141 Insert(fYankBuffer); 1142 ScrollToSelection(); 1143 } 1144 break; 1145 1146 default: 1147 BTextView::KeyDown(key, count); 1148 } 1149 } 1150 1151 1152 void 1153 TTextView::MakeFocus(bool focus) 1154 { 1155 if (!focus) { 1156 // ToDo: can someone please translate this? Otherwise I will remove it - axeld. 1157 // MakeFocus(false) は、IM も Inactive になり、そのまま確定される。 1158 // しかしこの場合、input_server が B_INPUT_METHOD_EVENT(B_INPUT_METHOD_STOPPED) 1159 // を送ってこないまま矛盾してしまうので、やむを得ずここでつじつまあわせ処理している。 1160 fInputMethodUndoState.active = false; 1161 // fInputMethodUndoBufferに溜まっている最後のデータがK_INSERTEDなら(確定)正規のバッファへ追加 1162 if (fInputMethodUndoBuffer.CountItems() > 0) { 1163 KUndoItem *item = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1); 1164 if (item->History == K_INSERTED) { 1165 fUndoBuffer.MakeNewUndoItem(); 1166 fUndoBuffer.AddUndo(item->RedoText, item->Length, item->Offset, item->History, item->CursorPos); 1167 fUndoBuffer.MakeNewUndoItem(); 1168 } 1169 fInputMethodUndoBuffer.MakeEmpty(); 1170 } 1171 } 1172 BTextView::MakeFocus(focus); 1173 1174 fParent->Focus(focus); 1175 } 1176 1177 1178 void 1179 TTextView::MessageReceived(BMessage *msg) 1180 { 1181 switch (msg->what) { 1182 case B_SIMPLE_DATA: 1183 { 1184 if (fIncoming) 1185 break; 1186 1187 BMessage message(REFS_RECEIVED); 1188 bool isEnclosure = false; 1189 bool inserted = false; 1190 1191 off_t len = 0; 1192 int32 end; 1193 int32 start; 1194 1195 int32 index = 0; 1196 entry_ref ref; 1197 while (msg->FindRef("refs", index++, &ref) == B_OK) { 1198 BFile file(&ref, B_READ_ONLY); 1199 if (file.InitCheck() == B_OK) { 1200 BNodeInfo node(&file); 1201 char type[B_FILE_NAME_LENGTH]; 1202 node.GetType(type); 1203 1204 off_t size = 0; 1205 file.GetSize(&size); 1206 1207 if (!strncasecmp(type, "text/", 5) && size > 0) { 1208 len += size; 1209 char *text = (char *)malloc(size); 1210 if (text == NULL) { 1211 puts("no memory!"); 1212 return; 1213 } 1214 if (file.Read(text, size) < B_OK) { 1215 puts("could not read from file"); 1216 free(text); 1217 continue; 1218 } 1219 if (!inserted) { 1220 GetSelection(&start, &end); 1221 Delete(); 1222 inserted = true; 1223 } 1224 1225 int32 offset = 0; 1226 for (int32 loop = 0; loop < size; loop++) { 1227 if (text[loop] == '\n') { 1228 Insert(&text[offset], loop - offset + 1); 1229 offset = loop + 1; 1230 } else if (text[loop] == '\r') { 1231 text[loop] = '\n'; 1232 Insert(&text[offset], loop - offset + 1); 1233 if ((loop + 1 < size) 1234 && (text[loop + 1] == '\n')) 1235 loop++; 1236 offset = loop + 1; 1237 } 1238 } 1239 free(text); 1240 } else { 1241 isEnclosure = true; 1242 message.AddRef("refs", &ref); 1243 } 1244 } 1245 } 1246 1247 if (index == 1) { 1248 // message doesn't contain any refs - maybe the parent class likes it 1249 BTextView::MessageReceived(msg); 1250 break; 1251 } 1252 1253 if (inserted) 1254 Select(start, start + len); 1255 if (isEnclosure) 1256 Window()->PostMessage(&message, Window()); 1257 break; 1258 } 1259 1260 case M_HEADER: 1261 msg->FindBool("header", &fHeader); 1262 SetText(NULL); 1263 LoadMessage(fMail, false, NULL); 1264 break; 1265 1266 case M_RAW: 1267 StopLoad(); 1268 1269 msg->FindBool("raw", &fRaw); 1270 SetText(NULL); 1271 LoadMessage(fMail, false, NULL); 1272 break; 1273 1274 case M_SELECT: 1275 if (IsSelectable()) 1276 Select(0, TextLength()); 1277 break; 1278 1279 case M_SAVE: 1280 Save(msg); 1281 break; 1282 1283 case B_NODE_MONITOR: 1284 { 1285 int32 opcode; 1286 if (msg->FindInt32("opcode", &opcode) == B_NO_ERROR) { 1287 dev_t device; 1288 if (msg->FindInt32("device", &device) < B_OK) 1289 break; 1290 ino_t inode; 1291 if (msg->FindInt64("node", &inode) < B_OK) 1292 break; 1293 1294 hyper_text *enclosure; 1295 for (int32 index = 0; 1296 (enclosure = (hyper_text *)fEnclosures->ItemAt(index++)) != NULL;) { 1297 if (device == enclosure->node.device 1298 && inode == enclosure->node.node) { 1299 if (opcode == B_ENTRY_REMOVED) { 1300 enclosure->saved = false; 1301 enclosure->have_ref = false; 1302 } else if (opcode == B_ENTRY_MOVED) { 1303 enclosure->ref.device = device; 1304 msg->FindInt64("to directory", &enclosure->ref.directory); 1305 1306 const char *name; 1307 msg->FindString("name", &name); 1308 enclosure->ref.set_name(name); 1309 } 1310 break; 1311 } 1312 } 1313 } 1314 break; 1315 } 1316 1317 // 1318 // Tracker has responded to a BMessage that was dragged out of 1319 // this email message. It has created a file for us, we just have to 1320 // put the stuff in it. 1321 // 1322 case B_COPY_TARGET: 1323 { 1324 BMessage data; 1325 if (msg->FindMessage("be:originator-data", &data) == B_OK) { 1326 entry_ref directory; 1327 const char *name; 1328 hyper_text *enclosure; 1329 1330 if (data.FindPointer("enclosure", (void **)&enclosure) == B_OK 1331 && msg->FindString("name", &name) == B_OK 1332 && msg->FindRef("directory", &directory) == B_OK) { 1333 switch (enclosure->type) { 1334 case TYPE_ENCLOSURE: 1335 case TYPE_BE_ENCLOSURE: 1336 { 1337 // 1338 // Enclosure. Decode the data and write it out. 1339 // 1340 BMessage saveMsg(M_SAVE); 1341 saveMsg.AddString("name", name); 1342 saveMsg.AddRef("directory", &directory); 1343 saveMsg.AddPointer("enclosure", enclosure); 1344 Save(&saveMsg, false); 1345 break; 1346 } 1347 1348 case TYPE_URL: 1349 { 1350 const char *replyType; 1351 if (msg->FindString("be:filetypes", &replyType) != B_OK) 1352 // drag recipient didn't ask for any specific type, 1353 // create a bookmark file as default 1354 replyType = "application/x-vnd.Be-bookmark"; 1355 1356 BDirectory dir(&directory); 1357 BFile file(&dir, name, B_READ_WRITE); 1358 if (file.InitCheck() == B_OK) { 1359 if (strcmp(replyType, "application/x-vnd.Be-bookmark") == 0) { 1360 // we got a request to create a bookmark, stuff 1361 // it with the url attribute 1362 file.WriteAttr("META:url", B_STRING_TYPE, 0, 1363 enclosure->name, strlen(enclosure->name) + 1); 1364 } else if (strcasecmp(replyType, "text/plain") == 0) { 1365 // create a plain text file, stuff it with 1366 // the url as text 1367 file.Write(enclosure->name, strlen(enclosure->name)); 1368 } 1369 1370 BNodeInfo fileInfo(&file); 1371 fileInfo.SetType(replyType); 1372 } 1373 break; 1374 } 1375 1376 case TYPE_MAILTO: 1377 { 1378 // 1379 // Add some attributes to the already created 1380 // person file. Strip out the 'mailto:' if 1381 // possible. 1382 // 1383 char *addrStart = enclosure->name; 1384 while (true) { 1385 if (*addrStart == ':') { 1386 addrStart++; 1387 break; 1388 } 1389 1390 if (*addrStart == '\0') { 1391 addrStart = enclosure->name; 1392 break; 1393 } 1394 1395 addrStart++; 1396 } 1397 1398 const char *replyType; 1399 if (msg->FindString("be:filetypes", &replyType) != B_OK) 1400 // drag recipient didn't ask for any specific type, 1401 // create a bookmark file as default 1402 replyType = "application/x-vnd.Be-bookmark"; 1403 1404 BDirectory dir(&directory); 1405 BFile file(&dir, name, B_READ_WRITE); 1406 if (file.InitCheck() == B_OK) { 1407 if (!strcmp(replyType, "application/x-person")) { 1408 // we got a request to create a bookmark, stuff 1409 // it with the address attribute 1410 file.WriteAttr("META:email", B_STRING_TYPE, 0, 1411 addrStart, strlen(enclosure->name) + 1); 1412 } else if (!strcasecmp(replyType, "text/plain")) { 1413 // create a plain text file, stuff it with the 1414 // email as text 1415 file.Write(addrStart, strlen(addrStart)); 1416 } 1417 1418 BNodeInfo fileInfo(&file); 1419 fileInfo.SetType(replyType); 1420 } 1421 break; 1422 } 1423 } 1424 } else { 1425 // 1426 // Assume this is handled by BTextView... 1427 // (Probably drag clipping.) 1428 // 1429 BTextView::MessageReceived(msg); 1430 } 1431 } 1432 break; 1433 } 1434 1435 case B_INPUT_METHOD_EVENT: 1436 { 1437 int32 im_op; 1438 if (msg->FindInt32("be:opcode", &im_op) == B_OK){ 1439 switch (im_op) { 1440 case B_INPUT_METHOD_STARTED: 1441 fInputMethodUndoState.replace = true; 1442 fInputMethodUndoState.active = true; 1443 break; 1444 case B_INPUT_METHOD_STOPPED: 1445 fInputMethodUndoState.active = false; 1446 if (fInputMethodUndoBuffer.CountItems() > 0) { 1447 KUndoItem *undo = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1); 1448 if (undo->History == K_INSERTED){ 1449 fUndoBuffer.MakeNewUndoItem(); 1450 fUndoBuffer.AddUndo(undo->RedoText, undo->Length, 1451 undo->Offset, undo->History, undo->CursorPos); 1452 fUndoBuffer.MakeNewUndoItem(); 1453 } 1454 fInputMethodUndoBuffer.MakeEmpty(); 1455 } 1456 break; 1457 case B_INPUT_METHOD_CHANGED: 1458 fInputMethodUndoState.active = true; 1459 break; 1460 case B_INPUT_METHOD_LOCATION_REQUEST: 1461 fInputMethodUndoState.active = true; 1462 break; 1463 } 1464 } 1465 BTextView::MessageReceived(msg); 1466 break; 1467 } 1468 1469 case M_REDO: 1470 Redo(); 1471 break; 1472 1473 default: 1474 BTextView::MessageReceived(msg); 1475 } 1476 } 1477 1478 1479 void 1480 TTextView::MouseDown(BPoint where) 1481 { 1482 if (IsEditable()) { 1483 BPoint point; 1484 uint32 buttons; 1485 GetMouse(&point, &buttons); 1486 if (gDictCount && (buttons == B_SECONDARY_MOUSE_BUTTON)) { 1487 int32 offset, start, end, length; 1488 const char *text = Text(); 1489 offset = OffsetAt(where); 1490 if (isalpha(text[offset])) { 1491 length = TextLength(); 1492 1493 //Find start and end of word 1494 //FindSpellBoundry(length, offset, &start, &end); 1495 1496 char c; 1497 bool isAlpha, isApost, isCap; 1498 int32 first; 1499 1500 for (first = offset; 1501 (first >= 0) && (((c = text[first]) == '\'') || isalpha(c)); 1502 first--) {} 1503 isCap = isupper(text[++first]); 1504 1505 for (start = offset, c = text[start], isAlpha = isalpha(c), isApost = (c=='\''); 1506 (start >= 0) && (isAlpha || (isApost 1507 && (((c = text[start+1]) != 's') || !isCap) && isalpha(c) 1508 && isalpha(text[start-1]))); 1509 start--, c = text[start], isAlpha = isalpha(c), isApost = (c == '\'')) {} 1510 start++; 1511 1512 for (end = offset, c = text[end], isAlpha = isalpha(c), isApost = (c == '\''); 1513 (end < length) && (isAlpha || (isApost 1514 && (((c = text[end + 1]) != 's') || !isCap) && isalpha(c))); 1515 end++, c = text[end], isAlpha = isalpha(c), isApost = (c == '\'')) {} 1516 1517 length = end - start; 1518 BString srcWord; 1519 srcWord.SetTo(text + start, length); 1520 1521 bool foundWord = false; 1522 BList matches; 1523 BString *string; 1524 1525 BMenuItem *menuItem; 1526 BPopUpMenu menu("Words", false, false); 1527 1528 int32 matchCount; 1529 for (int32 i = 0; i < gDictCount; i++) 1530 matchCount = gWords[i]->FindBestMatches(&matches, 1531 srcWord.String()); 1532 1533 if (matches.CountItems()) { 1534 sort_word_list(&matches, srcWord.String()); 1535 for (int32 i = 0; (string = (BString *)matches.ItemAt(i)) != NULL; i++) { 1536 menu.AddItem((menuItem = new BMenuItem(string->String(), NULL))); 1537 if (!strcasecmp(string->String(), srcWord.String())) { 1538 menuItem->SetEnabled(false); 1539 foundWord = true; 1540 } 1541 delete string; 1542 } 1543 } else { 1544 (menuItem = new BMenuItem("No matches", NULL))->SetEnabled(false); 1545 menu.AddItem(menuItem); 1546 } 1547 1548 BMenuItem *addItem = NULL; 1549 if (!foundWord && gUserDict >= 0) { 1550 menu.AddSeparatorItem(); 1551 addItem = new BMenuItem(TR("Add"), NULL); 1552 menu.AddItem(addItem); 1553 } 1554 1555 point = ConvertToScreen(where); 1556 if ((menuItem = menu.Go(point, false, false)) != NULL) { 1557 if (menuItem == addItem) { 1558 BString newItem(srcWord.String()); 1559 newItem << "\n"; 1560 gWords[gUserDict]->InitIndex(); 1561 gExactWords[gUserDict]->InitIndex(); 1562 gUserDictFile->Write(newItem.String(), newItem.Length()); 1563 gWords[gUserDict]->BuildIndex(); 1564 gExactWords[gUserDict]->BuildIndex(); 1565 1566 if (fSpellCheck) 1567 CheckSpelling(0, TextLength()); 1568 } else { 1569 int32 len = strlen(menuItem->Label()); 1570 Select(start, start); 1571 Delete(start, end); 1572 Insert(start, menuItem->Label(), len); 1573 Select(start+len, start+len); 1574 } 1575 } 1576 } 1577 return; 1578 } else if (fSpellCheck && IsEditable()) { 1579 int32 start, end; 1580 1581 GetSelection(&start, &end); 1582 FindSpellBoundry(1, start, &start, &end); 1583 CheckSpelling(start, end); 1584 } 1585 } else { 1586 // is not editable, look for enclosures/links 1587 1588 int32 clickOffset = OffsetAt(where); 1589 int32 items = fEnclosures->CountItems(); 1590 for (int32 loop = 0; loop < items; loop++) { 1591 hyper_text *enclosure = (hyper_text*) fEnclosures->ItemAt(loop); 1592 if (clickOffset < enclosure->text_start || clickOffset >= enclosure->text_end) 1593 continue; 1594 1595 // 1596 // The user is clicking on this attachment 1597 // 1598 1599 int32 start; 1600 int32 finish; 1601 Select(enclosure->text_start, enclosure->text_end); 1602 GetSelection(&start, &finish); 1603 Window()->UpdateIfNeeded(); 1604 1605 bool drag = false; 1606 bool held = false; 1607 uint32 buttons = 0; 1608 if (Window()->CurrentMessage()) { 1609 Window()->CurrentMessage()->FindInt32("buttons", 1610 (int32 *) &buttons); 1611 } 1612 1613 // 1614 // If this is the primary button, wait to see if the user is going 1615 // to single click, hold, or drag. 1616 // 1617 if (buttons != B_SECONDARY_MOUSE_BUTTON) { 1618 BPoint point = where; 1619 bigtime_t popupDelay; 1620 get_click_speed(&popupDelay); 1621 popupDelay *= 2; 1622 popupDelay += system_time(); 1623 while (buttons && abs((int)(point.x - where.x)) < 4 1624 && abs((int)(point.y - where.y)) < 4 1625 && system_time() < popupDelay) { 1626 snooze(10000); 1627 GetMouse(&point, &buttons); 1628 } 1629 1630 if (system_time() < popupDelay) { 1631 // 1632 // The user either dragged this or released the button. 1633 // check if it was dragged. 1634 // 1635 if (!(abs((int)(point.x - where.x)) < 4 1636 && abs((int)(point.y - where.y)) < 4) && buttons) 1637 drag = true; 1638 } else { 1639 // 1640 // The user held the button down. 1641 // 1642 held = true; 1643 } 1644 } 1645 1646 // 1647 // If the user has right clicked on this menu, 1648 // or held the button down on it for a while, 1649 // pop up a context menu. 1650 // 1651 if (buttons == B_SECONDARY_MOUSE_BUTTON || held) { 1652 // 1653 // Right mouse click... Display a menu 1654 // 1655 BPoint point = where; 1656 ConvertToScreen(&point); 1657 1658 BMenuItem *item; 1659 if ((enclosure->type != TYPE_ENCLOSURE) 1660 && (enclosure->type != TYPE_BE_ENCLOSURE)) 1661 item = fLinkMenu->Go(point, true); 1662 else 1663 item = fEnclosureMenu->Go(point, true); 1664 1665 BMessage *msg; 1666 if (item && (msg = item->Message()) != NULL) { 1667 if (msg->what == M_SAVE) { 1668 if (fPanel) 1669 fPanel->SetEnclosure(enclosure); 1670 else { 1671 fPanel = new TSavePanel(enclosure, this); 1672 fPanel->Window()->Show(); 1673 } 1674 } else if (msg->what == M_COPY) { 1675 // copy link location to clipboard 1676 1677 if (be_clipboard->Lock()) { 1678 be_clipboard->Clear(); 1679 1680 BMessage *clip; 1681 if ((clip = be_clipboard->Data()) != NULL) { 1682 clip->AddData("text/plain", B_MIME_TYPE, 1683 enclosure->name, strlen(enclosure->name)); 1684 be_clipboard->Commit(); 1685 } 1686 be_clipboard->Unlock(); 1687 } 1688 } else 1689 Open(enclosure); 1690 } 1691 } else { 1692 // 1693 // Left button. If the user single clicks, open this link. 1694 // Otherwise, initiate a drag. 1695 // 1696 if (drag) { 1697 BMessage dragMessage(B_SIMPLE_DATA); 1698 dragMessage.AddInt32("be:actions", B_COPY_TARGET); 1699 dragMessage.AddString("be:types", B_FILE_MIME_TYPE); 1700 switch (enclosure->type) { 1701 case TYPE_BE_ENCLOSURE: 1702 case TYPE_ENCLOSURE: 1703 // 1704 // Attachment. The type is specified in the message. 1705 // 1706 dragMessage.AddString("be:types", B_FILE_MIME_TYPE); 1707 dragMessage.AddString("be:filetypes", 1708 enclosure->content_type ? enclosure->content_type : ""); 1709 dragMessage.AddString("be:clip_name", enclosure->name); 1710 break; 1711 1712 case TYPE_URL: 1713 // 1714 // URL. The user can drag it into the tracker to 1715 // create a bookmark file. 1716 // 1717 dragMessage.AddString("be:types", B_FILE_MIME_TYPE); 1718 dragMessage.AddString("be:filetypes", 1719 "application/x-vnd.Be-bookmark"); 1720 dragMessage.AddString("be:filetypes", "text/plain"); 1721 dragMessage.AddString("be:clip_name", "Bookmark"); 1722 1723 dragMessage.AddString("be:url", enclosure->name); 1724 break; 1725 1726 case TYPE_MAILTO: 1727 // 1728 // Mailto address. The user can drag it into the 1729 // tracker to create a people file. 1730 // 1731 dragMessage.AddString("be:types", B_FILE_MIME_TYPE); 1732 dragMessage.AddString("be:filetypes", 1733 "application/x-person"); 1734 dragMessage.AddString("be:filetypes", "text/plain"); 1735 dragMessage.AddString("be:clip_name", "Person"); 1736 1737 dragMessage.AddString("be:email", enclosure->name); 1738 break; 1739 1740 default: 1741 // 1742 // Otherwise it doesn't have a type that I know how 1743 // to save. It won't have any types and if any 1744 // program wants to accept it, more power to them. 1745 // (tracker won't.) 1746 // 1747 dragMessage.AddString("be:clip_name", "Hyperlink"); 1748 } 1749 1750 BMessage data; 1751 data.AddPointer("enclosure", enclosure); 1752 dragMessage.AddMessage("be:originator-data", &data); 1753 1754 BRegion selectRegion; 1755 GetTextRegion(start, finish, &selectRegion); 1756 DragMessage(&dragMessage, selectRegion.Frame(), this); 1757 } else { 1758 // 1759 // User Single clicked on the attachment. Open it. 1760 // 1761 Open(enclosure); 1762 } 1763 } 1764 return; 1765 } 1766 } 1767 BTextView::MouseDown(where); 1768 } 1769 1770 1771 void 1772 TTextView::MouseMoved(BPoint where, uint32 code, const BMessage *msg) 1773 { 1774 int32 start = OffsetAt(where); 1775 1776 for (int32 loop = fEnclosures->CountItems(); loop-- > 0;) { 1777 hyper_text *enclosure = (hyper_text *)fEnclosures->ItemAt(loop); 1778 if ((start >= enclosure->text_start) && (start < enclosure->text_end)) { 1779 if (!fCursor) 1780 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 1781 fCursor = true; 1782 return; 1783 } 1784 } 1785 1786 if (fCursor) { 1787 SetViewCursor(B_CURSOR_I_BEAM); 1788 fCursor = false; 1789 } 1790 1791 BTextView::MouseMoved(where, code, msg); 1792 } 1793 1794 1795 void 1796 TTextView::ClearList() 1797 { 1798 hyper_text *enclosure; 1799 while ((enclosure = (hyper_text *)fEnclosures->FirstItem()) != NULL) { 1800 fEnclosures->RemoveItem(enclosure); 1801 1802 if (enclosure->name) 1803 free(enclosure->name); 1804 if (enclosure->content_type) 1805 free(enclosure->content_type); 1806 if (enclosure->encoding) 1807 free(enclosure->encoding); 1808 if (enclosure->have_ref && !enclosure->saved) { 1809 BEntry entry(&enclosure->ref); 1810 entry.Remove(); 1811 } 1812 1813 watch_node(&enclosure->node, B_STOP_WATCHING, this); 1814 free(enclosure); 1815 } 1816 } 1817 1818 1819 void 1820 TTextView::LoadMessage(BEmailMessage *mail, bool quoteIt, const char *text) 1821 { 1822 StopLoad(); 1823 1824 fMail = mail; 1825 1826 ClearList(); 1827 1828 MakeSelectable(true); 1829 MakeEditable(false); 1830 if (text) 1831 Insert(text, strlen(text)); 1832 1833 //attr_info attrInfo; 1834 TTextView::Reader *reader = new TTextView::Reader(fHeader, fRaw, quoteIt, fIncoming, 1835 text != NULL, true, 1836 // I removed the following, because I absolutely can't imagine why it's 1837 // there (the mail kit should be able to deal with non-compliant mails) 1838 // -- axeld. 1839 // fFile->GetAttrInfo(B_MAIL_ATTR_MIME, &attrInfo) == B_OK, 1840 this, mail, fEnclosures, fStopSem); 1841 1842 resume_thread(fThread = spawn_thread(Reader::Run, "reader", B_NORMAL_PRIORITY, reader)); 1843 } 1844 1845 1846 void 1847 TTextView::Open(hyper_text *enclosure) 1848 { 1849 switch (enclosure->type) { 1850 case TYPE_URL: 1851 { 1852 const struct {const char *urlType, *handler; } handlerTable[] = { 1853 {"http", B_URL_HTTP}, 1854 {"https", B_URL_HTTPS}, 1855 {"ftp", B_URL_FTP}, 1856 {"gopher", B_URL_GOPHER}, 1857 {"mailto", B_URL_MAILTO}, 1858 {"news", B_URL_NEWS}, 1859 {"nntp", B_URL_NNTP}, 1860 {"telnet", B_URL_TELNET}, 1861 {"rlogin", B_URL_RLOGIN}, 1862 {"tn3270", B_URL_TN3270}, 1863 {"wais", B_URL_WAIS}, 1864 {"file", B_URL_FILE}, 1865 {NULL, NULL} 1866 }; 1867 const char *handlerToLaunch = NULL; 1868 1869 const char *colonPos = strchr(enclosure->name, ':'); 1870 if (colonPos) { 1871 int urlTypeLength = colonPos - enclosure->name; 1872 1873 for (int32 index = 0; handlerTable[index].urlType; index++) { 1874 if (!strncasecmp(enclosure->name, 1875 handlerTable[index].urlType, urlTypeLength)) { 1876 handlerToLaunch = handlerTable[index].handler; 1877 break; 1878 } 1879 } 1880 } 1881 if (handlerToLaunch) { 1882 entry_ref appRef; 1883 if (be_roster->FindApp(handlerToLaunch, &appRef) != B_OK) 1884 handlerToLaunch = NULL; 1885 } 1886 if (!handlerToLaunch) 1887 handlerToLaunch = "application/x-vnd.Be-Bookmark"; 1888 1889 status_t result = be_roster->Launch(handlerToLaunch, 1, &enclosure->name); 1890 if (result != B_NO_ERROR && result != B_ALREADY_RUNNING) { 1891 beep(); 1892 (new BAlert("", 1893 TR("There is no installed handler for URL links."), 1894 "Sorry"))->Go(); 1895 } 1896 break; 1897 } 1898 1899 case TYPE_MAILTO: 1900 if (be_roster->Launch(B_MAIL_TYPE, 1, &enclosure->name) < B_OK) { 1901 char *argv[] = {"Mail", enclosure->name}; 1902 be_app->ArgvReceived(2, argv); 1903 } 1904 break; 1905 1906 case TYPE_ENCLOSURE: 1907 case TYPE_BE_ENCLOSURE: 1908 if (!enclosure->have_ref) { 1909 BPath path; 1910 if (find_directory(B_COMMON_TEMP_DIRECTORY, &path) == B_NO_ERROR) { 1911 BDirectory dir(path.Path()); 1912 if (dir.InitCheck() == B_NO_ERROR) { 1913 char name[B_FILE_NAME_LENGTH]; 1914 char baseName[B_FILE_NAME_LENGTH]; 1915 strcpy(baseName, enclosure->name ? enclosure->name : "enclosure"); 1916 strcpy(name, baseName); 1917 for (int32 index = 0; dir.Contains(name); index++) 1918 sprintf(name, "%s_%ld", baseName, index); 1919 1920 BEntry entry(path.Path()); 1921 entry_ref ref; 1922 entry.GetRef(&ref); 1923 1924 BMessage save(M_SAVE); 1925 save.AddRef("directory", &ref); 1926 save.AddString("name", name); 1927 save.AddPointer("enclosure", enclosure); 1928 if (Save(&save) != B_NO_ERROR) 1929 break; 1930 enclosure->saved = false; 1931 } 1932 } 1933 } 1934 1935 BMessenger tracker("application/x-vnd.Be-TRAK"); 1936 if (tracker.IsValid()) { 1937 BMessage openMsg(B_REFS_RECEIVED); 1938 openMsg.AddRef("refs", &enclosure->ref); 1939 tracker.SendMessage(&openMsg); 1940 } 1941 break; 1942 } 1943 } 1944 1945 1946 status_t 1947 TTextView::Save(BMessage *msg, bool makeNewFile) 1948 { 1949 const char *name; 1950 entry_ref ref; 1951 BFile file; 1952 BPath path; 1953 hyper_text *enclosure; 1954 status_t result = B_NO_ERROR; 1955 char entry_name[B_FILE_NAME_LENGTH]; 1956 1957 msg->FindString("name", &name); 1958 msg->FindRef("directory", &ref); 1959 msg->FindPointer("enclosure", (void **)&enclosure); 1960 1961 BDirectory dir; 1962 dir.SetTo(&ref); 1963 result = dir.InitCheck(); 1964 1965 if (result == B_OK) { 1966 if (makeNewFile) { 1967 // 1968 // Search for the file and delete it if it already exists. 1969 // (It may not, that's ok.) 1970 // 1971 BEntry entry; 1972 if (dir.FindEntry(name, &entry) == B_NO_ERROR) 1973 entry.Remove(); 1974 1975 if ((enclosure->have_ref) && (!enclosure->saved)) { 1976 entry.SetTo(&enclosure->ref); 1977 1978 // 1979 // Added true arg and entry_name so MoveTo clobbers as 1980 // before. This may not be the correct behaviour, but 1981 // it's the preserved behaviour. 1982 // 1983 entry.GetName(entry_name); 1984 result = entry.MoveTo(&dir, entry_name, true); 1985 if (result == B_NO_ERROR) { 1986 entry.Rename(name); 1987 entry.GetRef(&enclosure->ref); 1988 entry.GetNodeRef(&enclosure->node); 1989 enclosure->saved = true; 1990 return result; 1991 } 1992 } 1993 1994 if (result == B_NO_ERROR) { 1995 result = dir.CreateFile(name, &file); 1996 if (result == B_NO_ERROR && enclosure->content_type) { 1997 char type[B_MIME_TYPE_LENGTH]; 1998 1999 if (!strcasecmp(enclosure->content_type, "message/rfc822")) 2000 strcpy(type, "text/x-email"); 2001 else if (!strcasecmp(enclosure->content_type, "message/delivery-status")) 2002 strcpy(type, "text/plain"); 2003 else 2004 strcpy(type, enclosure->content_type); 2005 2006 BNodeInfo info(&file); 2007 info.SetType(type); 2008 } 2009 } 2010 } else { 2011 // 2012 // This file was dragged into the tracker or desktop. The file 2013 // already exists. 2014 // 2015 result = file.SetTo(&dir, name, B_WRITE_ONLY); 2016 } 2017 } 2018 2019 if (enclosure->component == NULL) 2020 result = B_ERROR; 2021 2022 if (result == B_NO_ERROR) { 2023 // 2024 // Write the data 2025 // 2026 enclosure->component->GetDecodedData(&file); 2027 2028 BEntry entry; 2029 dir.FindEntry(name, &entry); 2030 entry.GetRef(&enclosure->ref); 2031 enclosure->have_ref = true; 2032 enclosure->saved = true; 2033 entry.GetPath(&path); 2034 update_mime_info(path.Path(), false, true, 2035 !cistrcmp("application/octet-stream", enclosure->content_type ? enclosure->content_type : B_EMPTY_STRING)); 2036 entry.GetNodeRef(&enclosure->node); 2037 watch_node(&enclosure->node, B_WATCH_NAME, this); 2038 } 2039 2040 if (result != B_NO_ERROR) { 2041 beep(); 2042 (new BAlert("", TR("An error occurred trying to save the attachment."), 2043 TR("Sorry")))->Go(); 2044 } 2045 2046 return result; 2047 } 2048 2049 2050 void 2051 TTextView::StopLoad() 2052 { 2053 Window()->Unlock(); 2054 2055 thread_info info; 2056 if (fThread != 0 && get_thread_info(fThread, &info) == B_NO_ERROR) { 2057 fStopLoading = true; 2058 acquire_sem(fStopSem); 2059 int32 result; 2060 wait_for_thread(fThread, &result); 2061 fThread = 0; 2062 release_sem(fStopSem); 2063 fStopLoading = false; 2064 } 2065 2066 Window()->Lock(); 2067 } 2068 2069 2070 void 2071 TTextView::AddAsContent(BEmailMessage *mail, bool wrap, uint32 charset, mail_encoding encoding) 2072 { 2073 if (mail == NULL) 2074 return; 2075 2076 int32 textLength = TextLength(); 2077 const char *text = Text(); 2078 2079 BTextMailComponent *body = mail->Body(); 2080 if (body == NULL) { 2081 if (mail->SetBody(body = new BTextMailComponent()) < B_OK) 2082 return; 2083 } 2084 body->SetEncoding(encoding, charset); 2085 2086 // Just add the text as a whole if we can, or ... 2087 if (!wrap) { 2088 body->AppendText(text); 2089 return; 2090 } 2091 2092 // ... do word wrapping. 2093 2094 BWindow *window = Window(); 2095 char *saveText = strdup(text); 2096 BRect saveTextRect = TextRect(); 2097 2098 // do this before we start messing with the fonts 2099 // the user will never know... 2100 window->DisableUpdates(); 2101 Hide(); 2102 BScrollBar *vScroller = ScrollBar(B_VERTICAL); 2103 BScrollBar *hScroller = ScrollBar(B_HORIZONTAL); 2104 if (vScroller != NULL) 2105 vScroller->SetTarget((BView *)NULL); 2106 if (hScroller != NULL) 2107 hScroller->SetTarget((BView *)NULL); 2108 2109 // Temporarily set the font to a fixed width font for line wrapping 2110 // calculations. If the font doesn't have as many of the symbols as 2111 // the preferred font, go back to using the user's preferred font. 2112 2113 bool *boolArray; 2114 int missingCharactersFixedWidth = 0; 2115 int missingCharactersPreferredFont = 0; 2116 int32 numberOfCharacters; 2117 2118 numberOfCharacters = BString(text).CountChars(); 2119 if (numberOfCharacters > 0 2120 && (boolArray = (bool *)malloc(sizeof(bool) * numberOfCharacters)) != NULL) { 2121 memset(boolArray, 0, sizeof (bool) * numberOfCharacters); 2122 be_fixed_font->GetHasGlyphs(text, numberOfCharacters, boolArray); 2123 for (int i = 0; i < numberOfCharacters; i++) { 2124 if (!boolArray[i]) 2125 missingCharactersFixedWidth += 1; 2126 } 2127 2128 memset(boolArray, 0, sizeof (bool) * numberOfCharacters); 2129 fFont.GetHasGlyphs(text, numberOfCharacters, boolArray); 2130 for (int i = 0; i < numberOfCharacters; i++) { 2131 if (!boolArray[i]) 2132 missingCharactersPreferredFont += 1; 2133 } 2134 2135 free(boolArray); 2136 } 2137 2138 if (missingCharactersFixedWidth > missingCharactersPreferredFont) 2139 SetFontAndColor(0, textLength, &fFont); 2140 else // All things being equal, the fixed font is better for wrapping. 2141 SetFontAndColor(0, textLength, be_fixed_font); 2142 2143 // calculate a text rect that is 72 columns wide 2144 BRect newTextRect = saveTextRect; 2145 newTextRect.right = newTextRect.left + be_fixed_font->StringWidth("m") * 72; 2146 SetTextRect(newTextRect); 2147 2148 // hard-wrap, based on TextView's soft-wrapping 2149 int32 numLines = CountLines(); 2150 bool spaceMoved = false; 2151 char *content = (char *)malloc(textLength + numLines * 72); // more we'll ever need 2152 if (content != NULL) { 2153 int32 contentLength = 0; 2154 2155 for (int32 i = 0; i < numLines; i++) { 2156 int32 startOffset = OffsetAt(i); 2157 if (spaceMoved) { 2158 startOffset++; 2159 spaceMoved = false; 2160 } 2161 int32 endOffset = OffsetAt(i + 1); 2162 int32 lineLength = endOffset - startOffset; 2163 2164 // quick hack to not break URLs into several parts 2165 for (int32 pos = startOffset; pos < endOffset; pos++) { 2166 size_t urlLength; 2167 uint8 type = CheckForURL(text + pos, urlLength); 2168 if (type != 0) 2169 pos += urlLength; 2170 2171 if (pos > endOffset) { 2172 // find first break character after the URL 2173 for (; text[pos]; pos++) { 2174 if (isalnum(text[pos]) || isspace(text[pos])) 2175 break; 2176 } 2177 if (text[pos] && isspace(text[pos]) && text[pos] != '\n') 2178 pos++; 2179 2180 endOffset += pos - endOffset; 2181 lineLength = endOffset - startOffset; 2182 2183 // insert a newline (and the same number of quotes) after the 2184 // URL to make sure the rest of the text is properly wrapped 2185 2186 char buffer[64]; 2187 if (text[pos] == '\n') 2188 buffer[0] = '\0'; 2189 else 2190 strcpy(buffer, "\n"); 2191 2192 size_t quoteLength; 2193 CopyQuotes(text + startOffset, lineLength, buffer + strlen(buffer), quoteLength); 2194 2195 Insert(pos, buffer, strlen(buffer)); 2196 numLines = CountLines(); 2197 text = Text(); 2198 i++; 2199 } 2200 } 2201 if (text[endOffset - 1] != ' ' 2202 && text[endOffset - 1] != '\n' 2203 && text[endOffset] == ' ') { 2204 // make sure spaces will be part of this line 2205 endOffset++; 2206 lineLength++; 2207 spaceMoved = true; 2208 } 2209 2210 memcpy(content + contentLength, text + startOffset, lineLength); 2211 contentLength += lineLength; 2212 2213 // add a newline to every line except for the ones 2214 // that already end in newlines, and the last line 2215 if ((text[endOffset - 1] != '\n') && (i < (numLines - 1))) { 2216 content[contentLength++] = '\n'; 2217 2218 // copy quote level of the first line 2219 size_t quoteLength; 2220 CopyQuotes(text + startOffset, lineLength, content + contentLength, quoteLength); 2221 contentLength += quoteLength; 2222 } 2223 } 2224 content[contentLength] = '\0'; 2225 2226 body->AppendText(content); 2227 free(content); 2228 } 2229 2230 // reset the text rect and font 2231 SetTextRect(saveTextRect); 2232 SetText(saveText); 2233 free(saveText); 2234 SetFontAndColor(0, textLength, &fFont); 2235 2236 // should be OK to hook these back up now 2237 if (vScroller != NULL) 2238 vScroller->SetTarget(this); 2239 if (hScroller != NULL) 2240 hScroller->SetTarget(this); 2241 2242 Show(); 2243 window->EnableUpdates(); 2244 } 2245 2246 2247 // #pragma mark - 2248 2249 2250 TTextView::Reader::Reader(bool header, bool raw, bool quote, bool incoming, 2251 bool stripHeader, bool mime, TTextView *view, BEmailMessage *mail, 2252 BList *list, sem_id sem) 2253 : 2254 fHeader(header), 2255 fRaw(raw), 2256 fQuote(quote), 2257 fIncoming(incoming), 2258 fStripHeader(stripHeader), 2259 fMime(mime), 2260 fView(view), 2261 fMail(mail), 2262 fEnclosures(list), 2263 fStopSem(sem) 2264 { 2265 } 2266 2267 2268 bool 2269 TTextView::Reader::ParseMail(BMailContainer *container, 2270 BTextMailComponent *ignore) 2271 { 2272 int32 count = 0; 2273 for (int32 i = 0; i < container->CountComponents(); i++) { 2274 if (fView->fStopLoading) 2275 return false; 2276 2277 BMailComponent *component; 2278 if ((component = container->GetComponent(i)) == NULL) { 2279 if (fView->fStopLoading) 2280 return false; 2281 2282 hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text)); 2283 memset(enclosure, 0, sizeof(hyper_text)); 2284 2285 enclosure->type = TYPE_ENCLOSURE; 2286 2287 const char *name = "\n<Attachment: could not handle>\n"; 2288 2289 fView->GetSelection(&enclosure->text_start, &enclosure->text_end); 2290 enclosure->text_start++; 2291 enclosure->text_end += strlen(name) - 1; 2292 2293 Insert(name, strlen(name), true); 2294 fEnclosures->AddItem(enclosure); 2295 continue; 2296 } 2297 2298 count++; 2299 if (component == ignore) 2300 continue; 2301 2302 if (component->ComponentType() == B_MAIL_MULTIPART_CONTAINER) { 2303 BMIMEMultipartMailContainer *c = dynamic_cast<BMIMEMultipartMailContainer *>(container->GetComponent(i)); 2304 ASSERT(c != NULL); 2305 2306 if (!ParseMail(c, ignore)) 2307 count--; 2308 } else if (fIncoming) { 2309 hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text)); 2310 memset(enclosure, 0, sizeof(hyper_text)); 2311 2312 enclosure->type = TYPE_ENCLOSURE; 2313 enclosure->component = component; 2314 2315 BString name; 2316 char fileName[B_FILE_NAME_LENGTH]; 2317 strcpy(fileName, "untitled"); 2318 if (BMailAttachment *attachment = dynamic_cast <BMailAttachment *> (component)) 2319 attachment->FileName(fileName); 2320 2321 BPath path(fileName); 2322 enclosure->name = strdup(path.Leaf()); 2323 2324 BMimeType type; 2325 component->MIMEType(&type); 2326 enclosure->content_type = strdup(type.Type()); 2327 2328 char typeDescription[B_MIME_TYPE_LENGTH]; 2329 if (type.GetShortDescription(typeDescription) != B_OK) 2330 strcpy(typeDescription, type.Type() ? type.Type() : B_EMPTY_STRING); 2331 2332 name = "\n<Enclosure: "; 2333 name << enclosure->name << " (Type: " << typeDescription << ")>\n"; 2334 2335 fView->GetSelection(&enclosure->text_start, &enclosure->text_end); 2336 enclosure->text_start++; 2337 enclosure->text_end += strlen(name.String()) - 1; 2338 2339 Insert(name.String(), name.Length(), true); 2340 fEnclosures->AddItem(enclosure); 2341 } 2342 // default: 2343 // { 2344 // PlainTextBodyComponent *body = dynamic_cast<PlainTextBodyComponent *>(container->GetComponent(i)); 2345 // const char *text; 2346 // if (body && (text = body->Text()) != NULL) 2347 // Insert(text, strlen(text), false); 2348 // } 2349 } 2350 return count > 0; 2351 } 2352 2353 2354 bool 2355 TTextView::Reader::Process(const char *data, int32 data_len, bool isHeader) 2356 { 2357 char line[522]; 2358 int32 count = 0; 2359 2360 for (int32 loop = 0; loop < data_len; loop++) { 2361 if (fView->fStopLoading) 2362 return false; 2363 2364 if (fQuote && (!loop || (loop && data[loop - 1] == '\n'))) { 2365 strcpy(&line[count], QUOTE); 2366 count += strlen(QUOTE); 2367 } 2368 if (!fRaw && fIncoming && (loop < data_len - 7)) { 2369 size_t urlLength; 2370 BString url; 2371 uint8 type = CheckForURL(data + loop, urlLength, &url); 2372 2373 if (type) { 2374 if (!Insert(line, count, false, isHeader)) 2375 return false; 2376 count = 0; 2377 2378 hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text)); 2379 memset(enclosure, 0, sizeof(hyper_text)); 2380 fView->GetSelection(&enclosure->text_start, 2381 &enclosure->text_end); 2382 enclosure->type = type; 2383 enclosure->name = strdup(url.String()); 2384 if (enclosure->name == NULL) 2385 return false; 2386 2387 Insert(&data[loop], urlLength, true, isHeader); 2388 enclosure->text_end += urlLength; 2389 loop += urlLength - 1; 2390 2391 fEnclosures->AddItem(enclosure); 2392 continue; 2393 } 2394 } 2395 if (!fRaw && fMime && data[loop] == '=') { 2396 if ((loop) && (loop < data_len - 1) && (data[loop + 1] == '\r')) 2397 loop += 2; 2398 else 2399 line[count++] = data[loop]; 2400 } else if (data[loop] != '\r') 2401 line[count++] = data[loop]; 2402 2403 if (count > 511 || (count && loop == data_len - 1)) { 2404 if (!Insert(line, count, false, isHeader)) 2405 return false; 2406 count = 0; 2407 } 2408 } 2409 return true; 2410 } 2411 2412 2413 bool 2414 TTextView::Reader::Insert(const char *line, int32 count, bool isHyperLink, 2415 bool isHeader) 2416 { 2417 if (!count) 2418 return true; 2419 2420 BFont font(fView->Font()); 2421 TextRunArray style(count / 8 + 8); 2422 2423 if (fView->fColoredQuotes && !isHeader && !isHyperLink) { 2424 FillInQuoteTextRuns(fView, &fQuoteContext, line, count, font, 2425 &style.Array(), style.MaxEntries()); 2426 } else { 2427 text_run_array &array = style.Array(); 2428 array.count = 1; 2429 array.runs[0].offset = 0; 2430 if (isHeader) { 2431 array.runs[0].color = isHyperLink ? kHyperLinkColor : kHeaderColor; 2432 font.SetSize(font.Size() * 0.9); 2433 } else { 2434 array.runs[0].color = isHyperLink 2435 ? kHyperLinkColor : kNormalTextColor; 2436 } 2437 array.runs[0].font = font; 2438 } 2439 2440 if (!fView->Window()->Lock()) 2441 return false; 2442 2443 fView->Insert(fView->TextLength(), line, count, &style.Array()); 2444 2445 fView->Window()->Unlock(); 2446 return true; 2447 } 2448 2449 2450 status_t 2451 TTextView::Reader::Run(void *_this) 2452 { 2453 Reader *reader = (Reader *)_this; 2454 TTextView *view = reader->fView; 2455 char *msg = NULL; 2456 off_t size = 0; 2457 int32 len = 0; 2458 2459 if (!reader->Lock()) 2460 return B_INTERRUPTED; 2461 2462 BFile *file = dynamic_cast<BFile *>(reader->fMail->Data()); 2463 if (file != NULL) { 2464 len = header_len(file); 2465 2466 if (reader->fHeader) 2467 size = len; 2468 if (reader->fRaw || !reader->fMime) 2469 file->GetSize(&size); 2470 2471 if (size != 0 && (msg = (char *)malloc(size)) == NULL) 2472 goto done; 2473 file->Seek(0, 0); 2474 2475 if (msg) 2476 size = file->Read(msg, size); 2477 } 2478 2479 // show the header? 2480 if (reader->fHeader && len) { 2481 // strip all headers except "From", "To", "Reply-To", "Subject", and "Date" 2482 if (reader->fStripHeader) { 2483 const char *header = msg; 2484 char *buffer = NULL; 2485 2486 while (strncmp(header, "\r\n", 2)) { 2487 const char *eol = header; 2488 while ((eol = strstr(eol, "\r\n")) != NULL && isspace(eol[2])) 2489 eol += 2; 2490 if (eol == NULL) 2491 break; 2492 2493 eol += 2; // CR+LF belong to the line 2494 size_t length = eol - header; 2495 2496 buffer = (char *)realloc(buffer, length + 1); 2497 if (buffer == NULL) 2498 goto done; 2499 2500 memcpy(buffer, header, length); 2501 2502 length = rfc2047_to_utf8(&buffer, &length, length); 2503 2504 if (!strncasecmp(header, "Reply-To: ", 10) 2505 || !strncasecmp(header, "To: ", 4) 2506 || !strncasecmp(header, "From: ", 6) 2507 || !strncasecmp(header, "Subject: ", 8) 2508 || !strncasecmp(header, "Date: ", 6)) 2509 reader->Process(buffer, length, true); 2510 2511 header = eol; 2512 } 2513 free(buffer); 2514 reader->Process("\r\n", 2, true); 2515 } 2516 else if (!reader->Process(msg, len, true)) 2517 goto done; 2518 } 2519 2520 if (reader->fRaw) { 2521 if (!reader->Process((const char *)msg + len, size - len)) 2522 goto done; 2523 } else { 2524 //reader->fFile->Seek(0, 0); 2525 //BEmailMessage *mail = new BEmailMessage(reader->fFile); 2526 BEmailMessage *mail = reader->fMail; 2527 2528 // at first, insert the mail body 2529 BTextMailComponent *body = NULL; 2530 if (mail->BodyText() && !view->fStopLoading) { 2531 char *bodyText = const_cast<char *>(mail->BodyText()); 2532 int32 bodyLength = strlen(bodyText); 2533 body = mail->Body(); 2534 bool isHTML = false; 2535 2536 BMimeType type; 2537 if (body->MIMEType(&type) == B_OK && type == "text/html") { 2538 // strip out HTML tags 2539 char *t = bodyText, *a, *end = bodyText + bodyLength; 2540 bodyText = (char *)malloc(bodyLength + 1); 2541 isHTML = true; 2542 2543 // TODO: is it correct to assume that the text is in Latin-1? 2544 // because if it isn't, the code below won't work correctly... 2545 2546 for (a = bodyText; t < end; t++) { 2547 int32 c = *t; 2548 2549 // compact spaces 2550 bool space = false; 2551 while (c && (c == ' ' || c == '\t')) { 2552 c = *(++t); 2553 space = true; 2554 } 2555 if (space) { 2556 c = ' '; 2557 t--; 2558 } else if (FilterHTMLTag(c, &t, end)) // the tag filter 2559 continue; 2560 2561 Unicode2UTF8(c, &a); 2562 } 2563 2564 *a = 0; 2565 bodyLength = strlen(bodyText); 2566 body = NULL; // to add the HTML text as enclosure 2567 } 2568 if (!reader->Process(bodyText, bodyLength)) 2569 goto done; 2570 2571 if (isHTML) 2572 free(bodyText); 2573 } 2574 2575 if (!reader->ParseMail(mail, body)) 2576 goto done; 2577 2578 //reader->fView->fMail = mail; 2579 } 2580 2581 if (!view->fStopLoading && view->Window()->Lock()) { 2582 view->Select(0, 0); 2583 view->MakeSelectable(true); 2584 if (!reader->fIncoming) 2585 view->MakeEditable(true); 2586 2587 view->Window()->Unlock(); 2588 } 2589 2590 done: 2591 reader->Unlock(); 2592 2593 delete reader; 2594 free(msg); 2595 2596 return B_NO_ERROR; 2597 } 2598 2599 2600 status_t 2601 TTextView::Reader::Unlock() 2602 { 2603 return release_sem(fStopSem); 2604 } 2605 2606 2607 bool 2608 TTextView::Reader::Lock() 2609 { 2610 if (acquire_sem_etc(fStopSem, 1, B_TIMEOUT, 0) != B_NO_ERROR) 2611 return false; 2612 2613 return true; 2614 } 2615 2616 2617 //==================================================================== 2618 // #pragma mark - 2619 2620 2621 TSavePanel::TSavePanel(hyper_text *enclosure, TTextView *view) 2622 : BFilePanel(B_SAVE_PANEL) 2623 { 2624 fEnclosure = enclosure; 2625 fView = view; 2626 if (enclosure->name) 2627 SetSaveText(enclosure->name); 2628 } 2629 2630 2631 void 2632 TSavePanel::SendMessage(const BMessenger * /* messenger */, BMessage *msg) 2633 { 2634 const char *name = NULL; 2635 BMessage save(M_SAVE); 2636 entry_ref ref; 2637 2638 if ((!msg->FindRef("directory", &ref)) && (!msg->FindString("name", &name))) { 2639 save.AddPointer("enclosure", fEnclosure); 2640 save.AddString("name", name); 2641 save.AddRef("directory", &ref); 2642 fView->Window()->PostMessage(&save, fView); 2643 } 2644 } 2645 2646 2647 void 2648 TSavePanel::SetEnclosure(hyper_text *enclosure) 2649 { 2650 fEnclosure = enclosure; 2651 if (enclosure->name) 2652 SetSaveText(enclosure->name); 2653 else 2654 SetSaveText(""); 2655 2656 if (!IsShowing()) 2657 Show(); 2658 Window()->Activate(); 2659 } 2660 2661 2662 //-------------------------------------------------------------------- 2663 // #pragma mark - 2664 2665 2666 void 2667 TTextView::InsertText(const char *insertText, int32 length, int32 offset, 2668 const text_run_array *runs) 2669 { 2670 ContentChanged(); 2671 2672 // Undo function 2673 2674 int32 cursorPos, dummy; 2675 GetSelection(&cursorPos, &dummy); 2676 2677 if (fInputMethodUndoState.active) { 2678 // IMアクティブ時は、一旦別のバッファへ記憶 2679 fInputMethodUndoBuffer.AddUndo(insertText, length, offset, K_INSERTED, cursorPos); 2680 fInputMethodUndoState.replace = false; 2681 } else { 2682 if (fUndoState.replaced) { 2683 fUndoBuffer.AddUndo(insertText, length, offset, K_REPLACED, cursorPos); 2684 } else { 2685 if (length == 1 && insertText[0] == 0x0a) 2686 fUndoBuffer.MakeNewUndoItem(); 2687 2688 fUndoBuffer.AddUndo(insertText, length, offset, K_INSERTED, cursorPos); 2689 2690 if (length == 1 && insertText[0] == 0x0a) 2691 fUndoBuffer.MakeNewUndoItem(); 2692 } 2693 } 2694 2695 fUndoState.replaced = false; 2696 fUndoState.deleted = false; 2697 2698 struct text_runs : text_run_array { text_run _runs[1]; } style; 2699 if (runs == NULL && IsEditable()) { 2700 style.count = 1; 2701 style.runs[0].offset = 0; 2702 style.runs[0].font = fFont; 2703 style.runs[0].color = kNormalTextColor; 2704 runs = &style; 2705 } 2706 2707 BTextView::InsertText(insertText, length, offset, runs); 2708 2709 if (fSpellCheck && IsEditable()) 2710 { 2711 UpdateSpellMarks(offset, length); 2712 2713 rgb_color color; 2714 GetFontAndColor(offset - 1, NULL, &color); 2715 const char *text = Text(); 2716 2717 if (length > 1 2718 || isalpha(text[offset + 1]) 2719 || (!isalpha(text[offset]) && text[offset] != '\'') 2720 || (color.red == kSpellTextColor.red 2721 && color.green == kSpellTextColor.green 2722 && color.blue == kSpellTextColor.blue)) 2723 { 2724 int32 start, end; 2725 FindSpellBoundry(length, offset, &start, &end); 2726 2727 DSPELL(printf("Offset %ld, start %ld, end %ld\n", offset, start, end)); 2728 DSPELL(printf("\t\"%10.10s...\"\n", text + start)); 2729 2730 CheckSpelling(start, end); 2731 } 2732 } 2733 } 2734 2735 2736 void 2737 TTextView::DeleteText(int32 start, int32 finish) 2738 { 2739 ContentChanged(); 2740 2741 // Undo function 2742 int32 cursorPos, dummy; 2743 GetSelection(&cursorPos, &dummy); 2744 if (fInputMethodUndoState.active) { 2745 if (fInputMethodUndoState.replace) { 2746 fUndoBuffer.AddUndo(&Text()[start], finish - start, start, K_DELETED, cursorPos); 2747 fInputMethodUndoState.replace = false; 2748 } else { 2749 fInputMethodUndoBuffer.AddUndo(&Text()[start], finish - start, start, 2750 K_DELETED, cursorPos); 2751 } 2752 } else 2753 fUndoBuffer.AddUndo(&Text()[start], finish - start, start, K_DELETED, cursorPos); 2754 2755 fUndoState.deleted = true; 2756 fUndoState.replaced = true; 2757 2758 BTextView::DeleteText(start, finish); 2759 if (fSpellCheck && IsEditable()) { 2760 UpdateSpellMarks(start, start - finish); 2761 2762 int32 s, e; 2763 FindSpellBoundry(1, start, &s, &e); 2764 CheckSpelling(s, e); 2765 } 2766 } 2767 2768 2769 void 2770 TTextView::ContentChanged(void) 2771 { 2772 BLooper *looper = Looper(); 2773 if (looper == NULL) 2774 return; 2775 2776 BMessage msg(FIELD_CHANGED); 2777 msg.AddInt32("bitmask", FIELD_BODY); 2778 msg.AddPointer("source", this); 2779 looper->PostMessage(&msg); 2780 } 2781 2782 2783 void 2784 TTextView::CheckSpelling(int32 start, int32 end, int32 flags) 2785 { 2786 const char *text = Text(); 2787 const char *next, *endPtr, *word = NULL; 2788 int32 wordLength = 0, wordOffset; 2789 int32 nextHighlight = start; 2790 BString testWord; 2791 bool isCap = false; 2792 bool isAlpha; 2793 bool isApost; 2794 2795 for (next = text + start, endPtr = text + end; next <= endPtr; next++) { 2796 //printf("next=%c\n", *next); 2797 // ToDo: this has to be refined to other languages... 2798 // Alpha signifies the start of a word 2799 isAlpha = isalpha(*next); 2800 isApost = (*next == '\''); 2801 if (!word && isAlpha) { 2802 //printf("Found word start\n"); 2803 word = next; 2804 wordLength++; 2805 isCap = isupper(*word); 2806 } else if (word && (isAlpha || isApost) && !(isApost && !isalpha(next[1])) 2807 && !(isCap && isApost && (next[1] == 's'))) { 2808 // Word continues check 2809 wordLength++; 2810 //printf("Word continues...\n"); 2811 } else if (word) { 2812 // End of word reached 2813 2814 //printf("Word End\n"); 2815 // Don't check single characters 2816 if (wordLength > 1) { 2817 bool isUpper = true; 2818 2819 // Look for all uppercase 2820 for (int32 i = 0; i < wordLength; i++) { 2821 if (word[i] == '\'') 2822 break; 2823 2824 if (islower(word[i])) { 2825 isUpper = false; 2826 break; 2827 } 2828 } 2829 2830 // Don't check all uppercase words 2831 if (!isUpper) { 2832 bool foundMatch = false; 2833 wordOffset = word - text; 2834 testWord.SetTo(word, wordLength); 2835 2836 testWord = testWord.ToLower(); 2837 DSPELL(printf("Testing: \"%s\"\n", testWord.String())); 2838 2839 int32 key = -1; 2840 if (gDictCount) 2841 key = gExactWords[0]->GetKey(testWord.String()); 2842 2843 // Search all dictionaries 2844 for (int32 i = 0; i < gDictCount; i++) { 2845 if (gExactWords[i]->Lookup(key) >= 0) { 2846 foundMatch = true; 2847 break; 2848 } 2849 } 2850 2851 if (!foundMatch) { 2852 if (flags & S_CLEAR_ERRORS) 2853 RemoveSpellMark(nextHighlight, wordOffset); 2854 2855 if (flags & S_SHOW_ERRORS) 2856 AddSpellMark(wordOffset, wordOffset + wordLength); 2857 } else if (flags & S_CLEAR_ERRORS) 2858 RemoveSpellMark(nextHighlight, wordOffset + wordLength); 2859 2860 nextHighlight = wordOffset + wordLength; 2861 } 2862 } 2863 // Reset state to looking for word 2864 word = NULL; 2865 wordLength = 0; 2866 } 2867 } 2868 2869 if (nextHighlight <= end 2870 && (flags & S_CLEAR_ERRORS) != 0 2871 && nextHighlight < TextLength()) 2872 SetFontAndColor(nextHighlight, end, NULL, B_FONT_ALL, &kNormalTextColor); 2873 } 2874 2875 2876 void 2877 TTextView::FindSpellBoundry(int32 length, int32 offset, int32 *_start, int32 *_end) 2878 { 2879 int32 start, end, textLength; 2880 const char *text = Text(); 2881 textLength = TextLength(); 2882 2883 for (start = offset - 1; start >= 0 2884 && (isalpha(text[start]) || text[start] == '\''); start--) {} 2885 2886 start++; 2887 2888 for (end = offset + length; end < textLength 2889 && (isalpha(text[end]) || text[end] == '\''); end++) {} 2890 2891 *_start = start; 2892 *_end = end; 2893 } 2894 2895 2896 TTextView::spell_mark * 2897 TTextView::FindSpellMark(int32 start, int32 end, spell_mark **_previousMark) 2898 { 2899 spell_mark *lastMark = NULL; 2900 2901 for (spell_mark *spellMark = fFirstSpellMark; spellMark; spellMark = spellMark->next) { 2902 if (spellMark->start < end && spellMark->end > start) { 2903 if (_previousMark) 2904 *_previousMark = lastMark; 2905 return spellMark; 2906 } 2907 2908 lastMark = spellMark; 2909 } 2910 return NULL; 2911 } 2912 2913 2914 void 2915 TTextView::UpdateSpellMarks(int32 offset, int32 length) 2916 { 2917 DSPELL(printf("UpdateSpellMarks: offset = %ld, length = %ld\n", offset, length)); 2918 2919 spell_mark *spellMark; 2920 for (spellMark = fFirstSpellMark; spellMark; spellMark = spellMark->next) { 2921 DSPELL(printf("\tfound: %ld - %ld\n", spellMark->start, spellMark->end)); 2922 2923 if (spellMark->end < offset) 2924 continue; 2925 2926 if (spellMark->start > offset) 2927 spellMark->start += length; 2928 2929 spellMark->end += length; 2930 2931 DSPELL(printf("\t-> reset: %ld - %ld\n", spellMark->start, spellMark->end)); 2932 } 2933 } 2934 2935 2936 status_t 2937 TTextView::AddSpellMark(int32 start, int32 end) 2938 { 2939 DSPELL(printf("AddSpellMark: start = %ld, end = %ld\n", start, end)); 2940 2941 // check if there is already a mark for this passage 2942 spell_mark *spellMark = FindSpellMark(start, end); 2943 if (spellMark) { 2944 if (spellMark->start == start && spellMark->end == end) { 2945 DSPELL(printf("\tfound one\n")); 2946 return B_OK; 2947 } 2948 2949 DSPELL(printf("\tremove old one\n")); 2950 RemoveSpellMark(start, end); 2951 } 2952 2953 spellMark = (spell_mark *)malloc(sizeof(spell_mark)); 2954 if (spellMark == NULL) 2955 return B_NO_MEMORY; 2956 2957 spellMark->start = start; 2958 spellMark->end = end; 2959 spellMark->style = RunArray(start, end); 2960 2961 // set the spell marks appearance 2962 BFont font(fFont); 2963 font.SetFace(B_BOLD_FACE | B_ITALIC_FACE); 2964 SetFontAndColor(start, end, &font, B_FONT_ALL, &kSpellTextColor); 2965 2966 // add it to the queue 2967 spellMark->next = fFirstSpellMark; 2968 fFirstSpellMark = spellMark; 2969 2970 return B_OK; 2971 } 2972 2973 2974 bool 2975 TTextView::RemoveSpellMark(int32 start, int32 end) 2976 { 2977 DSPELL(printf("RemoveSpellMark: start = %ld, end = %ld\n", start, end)); 2978 2979 // find spell mark 2980 spell_mark *lastMark = NULL; 2981 spell_mark *spellMark = FindSpellMark(start, end, &lastMark); 2982 if (spellMark == NULL) { 2983 DSPELL(printf("\tnot found!\n")); 2984 return false; 2985 } 2986 2987 DSPELL(printf("\tfound: %ld - %ld\n", spellMark->start, spellMark->end)); 2988 2989 // dequeue the spell mark 2990 if (lastMark) 2991 lastMark->next = spellMark->next; 2992 else 2993 fFirstSpellMark = spellMark->next; 2994 2995 if (spellMark->start < start) 2996 start = spellMark->start; 2997 if (spellMark->end > end) 2998 end = spellMark->end; 2999 3000 // reset old text run array 3001 SetRunArray(start, end, spellMark->style); 3002 3003 free(spellMark->style); 3004 free(spellMark); 3005 3006 return true; 3007 } 3008 3009 3010 void 3011 TTextView::RemoveSpellMarks() 3012 { 3013 spell_mark *spellMark, *nextMark; 3014 3015 for (spellMark = fFirstSpellMark; spellMark; spellMark = nextMark) { 3016 nextMark = spellMark->next; 3017 3018 // reset old text run array 3019 SetRunArray(spellMark->start, spellMark->end, spellMark->style); 3020 3021 free(spellMark->style); 3022 free(spellMark); 3023 } 3024 3025 fFirstSpellMark = NULL; 3026 } 3027 3028 3029 void 3030 TTextView::EnableSpellCheck(bool enable) 3031 { 3032 if (fSpellCheck == enable) 3033 return; 3034 3035 fSpellCheck = enable; 3036 int32 textLength = TextLength(); 3037 if (fSpellCheck) { 3038 // work-around for a bug in the BTextView class 3039 // which causes lots of flicker 3040 int32 start, end; 3041 GetSelection(&start, &end); 3042 if (start != end) 3043 Select(start, start); 3044 3045 CheckSpelling(0, textLength); 3046 3047 if (start != end) 3048 Select(start, end); 3049 } 3050 else 3051 RemoveSpellMarks(); 3052 } 3053 3054 3055 void 3056 TTextView::WindowActivated(bool flag) 3057 { 3058 if (!flag) { 3059 // WindowActivated(false) は、IM も Inactive になり、そのまま確定される。 3060 // しかしこの場合、input_server が B_INPUT_METHOD_EVENT(B_INPUT_METHOD_STOPPED) 3061 // を送ってこないまま矛盾してしまうので、やむを得ずここでつじつまあわせ処理している。 3062 // OpenBeOSで修正されることを願って暫定処置としている。 3063 fInputMethodUndoState.active = false; 3064 // fInputMethodUndoBufferに溜まっている最後のデータがK_INSERTEDなら(確定)正規のバッファへ追加 3065 if (fInputMethodUndoBuffer.CountItems() > 0) { 3066 KUndoItem *item = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1); 3067 if (item->History == K_INSERTED) { 3068 fUndoBuffer.MakeNewUndoItem(); 3069 fUndoBuffer.AddUndo(item->RedoText, item->Length, item->Offset, 3070 item->History, item->CursorPos); 3071 fUndoBuffer.MakeNewUndoItem(); 3072 } 3073 fInputMethodUndoBuffer.MakeEmpty(); 3074 } 3075 } 3076 BTextView::WindowActivated(flag); 3077 } 3078 3079 3080 void 3081 TTextView::AddQuote(int32 start, int32 finish) 3082 { 3083 BRect rect = Bounds(); 3084 3085 int32 lineStart; 3086 GoToLine(CurrentLine()); 3087 GetSelection(&lineStart, &lineStart); 3088 3089 // make sure that we're changing the whole last line, too 3090 int32 lineEnd = finish > lineStart ? finish - 1 : finish; 3091 { 3092 const char *text = Text(); 3093 while (text[lineEnd] && text[lineEnd] != '\n') 3094 lineEnd++; 3095 } 3096 Select(lineStart, lineEnd); 3097 3098 int32 textLength = lineEnd - lineStart; 3099 char *text = (char *)malloc(textLength + 1); 3100 if (text == NULL) 3101 return; 3102 3103 GetText(lineStart, textLength, text); 3104 3105 int32 quoteLength = strlen(QUOTE); 3106 int32 targetLength = 0; 3107 char *target = NULL; 3108 int32 lastLine = 0; 3109 3110 for (int32 index = 0; index < textLength; index++) { 3111 if (text[index] == '\n' || index == textLength - 1) { 3112 // add quote to this line 3113 int32 lineLength = index - lastLine + 1; 3114 3115 target = (char *)realloc(target, targetLength + lineLength + quoteLength); 3116 if (target == NULL) { 3117 // free the old buffer? 3118 free(text); 3119 return; 3120 } 3121 3122 // copy the quote sign 3123 memcpy(&target[targetLength], QUOTE, quoteLength); 3124 targetLength += quoteLength; 3125 3126 // copy the rest of the line 3127 memcpy(&target[targetLength], &text[lastLine], lineLength); 3128 targetLength += lineLength; 3129 3130 lastLine = index + 1; 3131 } 3132 } 3133 3134 // replace with quoted text 3135 free(text); 3136 Delete(); 3137 3138 if (fColoredQuotes) { 3139 const BFont *font = Font(); 3140 TextRunArray style(targetLength / 8 + 8); 3141 3142 FillInQuoteTextRuns(NULL, NULL, target, targetLength, font, 3143 &style.Array(), style.MaxEntries()); 3144 Insert(target, targetLength, &style.Array()); 3145 } else 3146 Insert(target, targetLength); 3147 3148 free(target); 3149 3150 // redo the old selection (compute the new start if necessary) 3151 Select(start + quoteLength, finish + (targetLength - textLength)); 3152 3153 ScrollTo(rect.LeftTop()); 3154 } 3155 3156 3157 void 3158 TTextView::RemoveQuote(int32 start, int32 finish) 3159 { 3160 BRect rect = Bounds(); 3161 3162 GoToLine(CurrentLine()); 3163 int32 lineStart; 3164 GetSelection(&lineStart, &lineStart); 3165 3166 // make sure that we're changing the whole last line, too 3167 int32 lineEnd = finish > lineStart ? finish - 1 : finish; 3168 const char *text = Text(); 3169 while (text[lineEnd] && text[lineEnd] != '\n') 3170 lineEnd++; 3171 3172 Select(lineStart, lineEnd); 3173 3174 int32 length = lineEnd - lineStart; 3175 char *target = (char *)malloc(length + 1); 3176 if (target == NULL) 3177 return; 3178 3179 int32 quoteLength = strlen(QUOTE); 3180 int32 removed = 0; 3181 text += lineStart; 3182 3183 for (int32 index = 0; index < length;) { 3184 // find out the length of the current line 3185 int32 lineLength = 0; 3186 while (index + lineLength < length && text[lineLength] != '\n') 3187 lineLength++; 3188 3189 // include the newline to be part of this line 3190 if (text[lineLength] == '\n' && index + lineLength + 1 < length) 3191 lineLength++; 3192 3193 if (!strncmp(text, QUOTE, quoteLength)) { 3194 // remove quote 3195 length -= quoteLength; 3196 removed += quoteLength; 3197 3198 lineLength -= quoteLength; 3199 text += quoteLength; 3200 } 3201 3202 if (lineLength == 0) { 3203 target[index] = '\0'; 3204 break; 3205 } 3206 3207 memcpy(&target[index], text, lineLength); 3208 3209 text += lineLength; 3210 index += lineLength; 3211 } 3212 3213 if (removed) { 3214 Delete(); 3215 3216 if (fColoredQuotes) { 3217 const BFont *font = Font(); 3218 TextRunArray style(length / 8 + 8); 3219 3220 FillInQuoteTextRuns(NULL, NULL, target, length, font, 3221 &style.Array(), style.MaxEntries()); 3222 Insert(target, length, &style.Array()); 3223 } else 3224 Insert(target, length); 3225 3226 // redo old selection 3227 bool noSelection = start == finish; 3228 3229 if (start > lineStart + quoteLength) 3230 start -= quoteLength; 3231 else 3232 start = lineStart; 3233 3234 if (noSelection) 3235 finish = start; 3236 else 3237 finish -= removed; 3238 } 3239 3240 free(target); 3241 3242 Select(start, finish); 3243 ScrollTo(rect.LeftTop()); 3244 } 3245 3246 3247 void 3248 TTextView::Undo(BClipboard */*clipboard*/) 3249 { 3250 if (fInputMethodUndoState.active) 3251 return; 3252 3253 int32 length, offset, cursorPos; 3254 undo_type history; 3255 char *text; 3256 status_t status; 3257 3258 status = fUndoBuffer.Undo(&text, &length, &offset, &history, &cursorPos); 3259 if (status == B_OK) { 3260 fUndoBuffer.Off(); 3261 3262 switch (history) { 3263 case K_INSERTED: 3264 BTextView::Delete(offset, offset + length); 3265 Select(offset, offset); 3266 break; 3267 3268 case K_DELETED: 3269 BTextView::Insert(offset, text, length); 3270 Select(offset, offset + length); 3271 break; 3272 3273 case K_REPLACED: 3274 BTextView::Delete(offset, offset + length); 3275 status = fUndoBuffer.Undo(&text, &length, &offset, &history, &cursorPos); 3276 if (status == B_OK && history == K_DELETED) { 3277 BTextView::Insert(offset, text, length); 3278 Select(offset, offset + length); 3279 } else { 3280 ::beep(); 3281 (new BAlert("", 3282 TR("Inconsistency occurred in the undo/redo buffer."), 3283 TR("OK")))->Go(); 3284 } 3285 break; 3286 } 3287 ScrollToSelection(); 3288 ContentChanged(); 3289 fUndoBuffer.On(); 3290 } 3291 } 3292 3293 3294 void 3295 TTextView::Redo() 3296 { 3297 if (fInputMethodUndoState.active) 3298 return; 3299 3300 int32 length, offset, cursorPos; 3301 undo_type history; 3302 char *text; 3303 status_t status; 3304 bool replaced; 3305 3306 status = fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced); 3307 if (status == B_OK) { 3308 fUndoBuffer.Off(); 3309 3310 switch (history) { 3311 case K_INSERTED: 3312 BTextView::Insert(offset, text, length); 3313 Select(offset, offset + length); 3314 break; 3315 3316 case K_DELETED: 3317 BTextView::Delete(offset, offset + length); 3318 if (replaced) { 3319 fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced); 3320 BTextView::Insert(offset, text, length); 3321 } 3322 Select(offset, offset + length); 3323 break; 3324 3325 case K_REPLACED: 3326 ::beep(); 3327 (new BAlert("", 3328 TR("Inconsistency occurred in the undo/redo buffer."), 3329 TR("OK")))->Go(); 3330 break; 3331 } 3332 ScrollToSelection(); 3333 ContentChanged(); 3334 fUndoBuffer.On(); 3335 } 3336 } 3337 3338