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