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<"; 2335 name.Append(B_TRANSLATE_COMMENT("Enclosure: %name% (Type: %type%)", 2336 "Don't translate the variables %name% and %type%.")); 2337 name.Append(">\n"); 2338 name.ReplaceFirst("%name%", enclosure->name); 2339 name.ReplaceFirst("%type%", typeDescription); 2340 2341 fView->GetSelection(&enclosure->text_start, &enclosure->text_end); 2342 enclosure->text_start++; 2343 enclosure->text_end += strlen(name.String()) - 1; 2344 2345 Insert(name.String(), name.Length(), true); 2346 fEnclosures->AddItem(enclosure); 2347 } 2348 // default: 2349 // { 2350 // PlainTextBodyComponent *body = dynamic_cast<PlainTextBodyComponent *>(container->GetComponent(i)); 2351 // const char *text; 2352 // if (body && (text = body->Text()) != NULL) 2353 // Insert(text, strlen(text), false); 2354 // } 2355 } 2356 return count > 0; 2357 } 2358 2359 2360 bool 2361 TTextView::Reader::Process(const char *data, int32 data_len, bool isHeader) 2362 { 2363 char line[522]; 2364 int32 count = 0; 2365 2366 for (int32 loop = 0; loop < data_len; loop++) { 2367 if (fView->fStopLoading) 2368 return false; 2369 2370 if (fQuote && (!loop || (loop && data[loop - 1] == '\n'))) { 2371 strcpy(&line[count], QUOTE); 2372 count += strlen(QUOTE); 2373 } 2374 if (!fRaw && fIncoming && (loop < data_len - 7)) { 2375 size_t urlLength; 2376 BString url; 2377 uint8 type = CheckForURL(data + loop, urlLength, &url); 2378 2379 if (type) { 2380 if (!Insert(line, count, false, isHeader)) 2381 return false; 2382 count = 0; 2383 2384 hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text)); 2385 memset(enclosure, 0, sizeof(hyper_text)); 2386 fView->GetSelection(&enclosure->text_start, 2387 &enclosure->text_end); 2388 enclosure->type = type; 2389 enclosure->name = strdup(url.String()); 2390 if (enclosure->name == NULL) 2391 return false; 2392 2393 Insert(&data[loop], urlLength, true, isHeader); 2394 enclosure->text_end += urlLength; 2395 loop += urlLength - 1; 2396 2397 fEnclosures->AddItem(enclosure); 2398 continue; 2399 } 2400 } 2401 if (!fRaw && fMime && data[loop] == '=') { 2402 if ((loop) && (loop < data_len - 1) && (data[loop + 1] == '\r')) 2403 loop += 2; 2404 else 2405 line[count++] = data[loop]; 2406 } else if (data[loop] != '\r') 2407 line[count++] = data[loop]; 2408 2409 if (count > 511 || (count && loop == data_len - 1)) { 2410 if (!Insert(line, count, false, isHeader)) 2411 return false; 2412 count = 0; 2413 } 2414 } 2415 return true; 2416 } 2417 2418 2419 bool 2420 TTextView::Reader::Insert(const char *line, int32 count, bool isHyperLink, 2421 bool isHeader) 2422 { 2423 if (!count) 2424 return true; 2425 2426 BFont font(fView->Font()); 2427 TextRunArray style(count / 8 + 8); 2428 2429 if (fView->fColoredQuotes && !isHeader && !isHyperLink) { 2430 FillInQuoteTextRuns(fView, &fQuoteContext, line, count, font, 2431 &style.Array(), style.MaxEntries()); 2432 } else { 2433 text_run_array &array = style.Array(); 2434 array.count = 1; 2435 array.runs[0].offset = 0; 2436 if (isHeader) { 2437 array.runs[0].color = isHyperLink ? kHyperLinkColor : kHeaderColor; 2438 font.SetSize(font.Size() * 0.9); 2439 } else { 2440 array.runs[0].color = isHyperLink 2441 ? kHyperLinkColor : kNormalTextColor; 2442 } 2443 array.runs[0].font = font; 2444 } 2445 2446 if (!fView->Window()->Lock()) 2447 return false; 2448 2449 fView->Insert(fView->TextLength(), line, count, &style.Array()); 2450 2451 fView->Window()->Unlock(); 2452 return true; 2453 } 2454 2455 2456 status_t 2457 TTextView::Reader::Run(void *_this) 2458 { 2459 Reader *reader = (Reader *)_this; 2460 TTextView *view = reader->fView; 2461 char *msg = NULL; 2462 off_t size = 0; 2463 int32 len = 0; 2464 2465 if (!reader->Lock()) 2466 return B_INTERRUPTED; 2467 2468 BFile *file = dynamic_cast<BFile *>(reader->fMail->Data()); 2469 if (file != NULL) { 2470 len = header_len(file); 2471 2472 if (reader->fHeader) 2473 size = len; 2474 if (reader->fRaw || !reader->fMime) 2475 file->GetSize(&size); 2476 2477 if (size != 0 && (msg = (char *)malloc(size)) == NULL) 2478 goto done; 2479 file->Seek(0, 0); 2480 2481 if (msg) 2482 size = file->Read(msg, size); 2483 } 2484 2485 // show the header? 2486 if (reader->fHeader && len) { 2487 // strip all headers except "From", "To", "Reply-To", "Subject", and "Date" 2488 if (reader->fStripHeader) { 2489 const char *header = msg; 2490 char *buffer = NULL; 2491 2492 while (strncmp(header, "\r\n", 2)) { 2493 const char *eol = header; 2494 while ((eol = strstr(eol, "\r\n")) != NULL && isspace(eol[2])) 2495 eol += 2; 2496 if (eol == NULL) 2497 break; 2498 2499 eol += 2; // CR+LF belong to the line 2500 size_t length = eol - header; 2501 2502 buffer = (char *)realloc(buffer, length + 1); 2503 if (buffer == NULL) 2504 goto done; 2505 2506 memcpy(buffer, header, length); 2507 2508 length = rfc2047_to_utf8(&buffer, &length, length); 2509 2510 if (!strncasecmp(header, "Reply-To: ", 10) 2511 || !strncasecmp(header, "To: ", 4) 2512 || !strncasecmp(header, "From: ", 6) 2513 || !strncasecmp(header, "Subject: ", 8) 2514 || !strncasecmp(header, "Date: ", 6)) 2515 reader->Process(buffer, length, true); 2516 2517 header = eol; 2518 } 2519 free(buffer); 2520 reader->Process("\r\n", 2, true); 2521 } 2522 else if (!reader->Process(msg, len, true)) 2523 goto done; 2524 } 2525 2526 if (reader->fRaw) { 2527 if (!reader->Process((const char *)msg + len, size - len)) 2528 goto done; 2529 } else { 2530 //reader->fFile->Seek(0, 0); 2531 //BEmailMessage *mail = new BEmailMessage(reader->fFile); 2532 BEmailMessage *mail = reader->fMail; 2533 2534 // at first, insert the mail body 2535 BTextMailComponent *body = NULL; 2536 if (mail->BodyText() && !view->fStopLoading) { 2537 char *bodyText = const_cast<char *>(mail->BodyText()); 2538 int32 bodyLength = strlen(bodyText); 2539 body = mail->Body(); 2540 bool isHTML = false; 2541 2542 BMimeType type; 2543 if (body->MIMEType(&type) == B_OK && type == "text/html") { 2544 // strip out HTML tags 2545 char *t = bodyText, *a, *end = bodyText + bodyLength; 2546 bodyText = (char *)malloc(bodyLength + 1); 2547 isHTML = true; 2548 2549 // TODO: is it correct to assume that the text is in Latin-1? 2550 // because if it isn't, the code below won't work correctly... 2551 2552 for (a = bodyText; t < end; t++) { 2553 int32 c = *t; 2554 2555 // compact spaces 2556 bool space = false; 2557 while (c && (c == ' ' || c == '\t')) { 2558 c = *(++t); 2559 space = true; 2560 } 2561 if (space) { 2562 c = ' '; 2563 t--; 2564 } else if (FilterHTMLTag(c, &t, end)) // the tag filter 2565 continue; 2566 2567 Unicode2UTF8(c, &a); 2568 } 2569 2570 *a = 0; 2571 bodyLength = strlen(bodyText); 2572 body = NULL; // to add the HTML text as enclosure 2573 } 2574 if (!reader->Process(bodyText, bodyLength)) 2575 goto done; 2576 2577 if (isHTML) 2578 free(bodyText); 2579 } 2580 2581 if (!reader->ParseMail(mail, body)) 2582 goto done; 2583 2584 //reader->fView->fMail = mail; 2585 } 2586 2587 if (!view->fStopLoading && view->Window()->Lock()) { 2588 view->Select(0, 0); 2589 view->MakeSelectable(true); 2590 if (!reader->fIncoming) 2591 view->MakeEditable(true); 2592 2593 view->Window()->Unlock(); 2594 } 2595 2596 done: 2597 reader->Unlock(); 2598 2599 delete reader; 2600 free(msg); 2601 2602 return B_NO_ERROR; 2603 } 2604 2605 2606 status_t 2607 TTextView::Reader::Unlock() 2608 { 2609 return release_sem(fStopSem); 2610 } 2611 2612 2613 bool 2614 TTextView::Reader::Lock() 2615 { 2616 if (acquire_sem_etc(fStopSem, 1, B_TIMEOUT, 0) != B_NO_ERROR) 2617 return false; 2618 2619 return true; 2620 } 2621 2622 2623 //==================================================================== 2624 // #pragma mark - 2625 2626 2627 TSavePanel::TSavePanel(hyper_text *enclosure, TTextView *view) 2628 : BFilePanel(B_SAVE_PANEL) 2629 { 2630 fEnclosure = enclosure; 2631 fView = view; 2632 if (enclosure->name) 2633 SetSaveText(enclosure->name); 2634 } 2635 2636 2637 void 2638 TSavePanel::SendMessage(const BMessenger * /* messenger */, BMessage *msg) 2639 { 2640 const char *name = NULL; 2641 BMessage save(M_SAVE); 2642 entry_ref ref; 2643 2644 if ((!msg->FindRef("directory", &ref)) && (!msg->FindString("name", &name))) { 2645 save.AddPointer("enclosure", fEnclosure); 2646 save.AddString("name", name); 2647 save.AddRef("directory", &ref); 2648 fView->Window()->PostMessage(&save, fView); 2649 } 2650 } 2651 2652 2653 void 2654 TSavePanel::SetEnclosure(hyper_text *enclosure) 2655 { 2656 fEnclosure = enclosure; 2657 if (enclosure->name) 2658 SetSaveText(enclosure->name); 2659 else 2660 SetSaveText(""); 2661 2662 if (!IsShowing()) 2663 Show(); 2664 Window()->Activate(); 2665 } 2666 2667 2668 //-------------------------------------------------------------------- 2669 // #pragma mark - 2670 2671 2672 void 2673 TTextView::InsertText(const char *insertText, int32 length, int32 offset, 2674 const text_run_array *runs) 2675 { 2676 ContentChanged(); 2677 2678 // Undo function 2679 2680 int32 cursorPos, dummy; 2681 GetSelection(&cursorPos, &dummy); 2682 2683 if (fInputMethodUndoState.active) { 2684 // IMアクティブ時は、一旦別のバッファへ記憶 2685 fInputMethodUndoBuffer.AddUndo(insertText, length, offset, K_INSERTED, cursorPos); 2686 fInputMethodUndoState.replace = false; 2687 } else { 2688 if (fUndoState.replaced) { 2689 fUndoBuffer.AddUndo(insertText, length, offset, K_REPLACED, cursorPos); 2690 } else { 2691 if (length == 1 && insertText[0] == 0x0a) 2692 fUndoBuffer.MakeNewUndoItem(); 2693 2694 fUndoBuffer.AddUndo(insertText, length, offset, K_INSERTED, cursorPos); 2695 2696 if (length == 1 && insertText[0] == 0x0a) 2697 fUndoBuffer.MakeNewUndoItem(); 2698 } 2699 } 2700 2701 fUndoState.replaced = false; 2702 fUndoState.deleted = false; 2703 2704 struct text_runs : text_run_array { text_run _runs[1]; } style; 2705 if (runs == NULL && IsEditable()) { 2706 style.count = 1; 2707 style.runs[0].offset = 0; 2708 style.runs[0].font = fFont; 2709 style.runs[0].color = kNormalTextColor; 2710 runs = &style; 2711 } 2712 2713 BTextView::InsertText(insertText, length, offset, runs); 2714 2715 if (fSpellCheck && IsEditable()) 2716 { 2717 UpdateSpellMarks(offset, length); 2718 2719 rgb_color color; 2720 GetFontAndColor(offset - 1, NULL, &color); 2721 const char *text = Text(); 2722 2723 if (length > 1 2724 || isalpha(text[offset + 1]) 2725 || (!isalpha(text[offset]) && text[offset] != '\'') 2726 || (color.red == kSpellTextColor.red 2727 && color.green == kSpellTextColor.green 2728 && color.blue == kSpellTextColor.blue)) 2729 { 2730 int32 start, end; 2731 FindSpellBoundry(length, offset, &start, &end); 2732 2733 DSPELL(printf("Offset %ld, start %ld, end %ld\n", offset, start, end)); 2734 DSPELL(printf("\t\"%10.10s...\"\n", text + start)); 2735 2736 CheckSpelling(start, end); 2737 } 2738 } 2739 } 2740 2741 2742 void 2743 TTextView::DeleteText(int32 start, int32 finish) 2744 { 2745 ContentChanged(); 2746 2747 // Undo function 2748 int32 cursorPos, dummy; 2749 GetSelection(&cursorPos, &dummy); 2750 if (fInputMethodUndoState.active) { 2751 if (fInputMethodUndoState.replace) { 2752 fUndoBuffer.AddUndo(&Text()[start], finish - start, start, K_DELETED, cursorPos); 2753 fInputMethodUndoState.replace = false; 2754 } else { 2755 fInputMethodUndoBuffer.AddUndo(&Text()[start], finish - start, start, 2756 K_DELETED, cursorPos); 2757 } 2758 } else 2759 fUndoBuffer.AddUndo(&Text()[start], finish - start, start, K_DELETED, cursorPos); 2760 2761 fUndoState.deleted = true; 2762 fUndoState.replaced = true; 2763 2764 BTextView::DeleteText(start, finish); 2765 if (fSpellCheck && IsEditable()) { 2766 UpdateSpellMarks(start, start - finish); 2767 2768 int32 s, e; 2769 FindSpellBoundry(1, start, &s, &e); 2770 CheckSpelling(s, e); 2771 } 2772 } 2773 2774 2775 void 2776 TTextView::ContentChanged(void) 2777 { 2778 BLooper *looper = Looper(); 2779 if (looper == NULL) 2780 return; 2781 2782 BMessage msg(FIELD_CHANGED); 2783 msg.AddInt32("bitmask", FIELD_BODY); 2784 msg.AddPointer("source", this); 2785 looper->PostMessage(&msg); 2786 } 2787 2788 2789 void 2790 TTextView::CheckSpelling(int32 start, int32 end, int32 flags) 2791 { 2792 const char *text = Text(); 2793 const char *next, *endPtr, *word = NULL; 2794 int32 wordLength = 0, wordOffset; 2795 int32 nextHighlight = start; 2796 BString testWord; 2797 bool isCap = false; 2798 bool isAlpha; 2799 bool isApost; 2800 2801 for (next = text + start, endPtr = text + end; next <= endPtr; next++) { 2802 //printf("next=%c\n", *next); 2803 // ToDo: this has to be refined to other languages... 2804 // Alpha signifies the start of a word 2805 isAlpha = isalpha(*next); 2806 isApost = (*next == '\''); 2807 if (!word && isAlpha) { 2808 //printf("Found word start\n"); 2809 word = next; 2810 wordLength++; 2811 isCap = isupper(*word); 2812 } else if (word && (isAlpha || isApost) && !(isApost && !isalpha(next[1])) 2813 && !(isCap && isApost && (next[1] == 's'))) { 2814 // Word continues check 2815 wordLength++; 2816 //printf("Word continues...\n"); 2817 } else if (word) { 2818 // End of word reached 2819 2820 //printf("Word End\n"); 2821 // Don't check single characters 2822 if (wordLength > 1) { 2823 bool isUpper = true; 2824 2825 // Look for all uppercase 2826 for (int32 i = 0; i < wordLength; i++) { 2827 if (word[i] == '\'') 2828 break; 2829 2830 if (islower(word[i])) { 2831 isUpper = false; 2832 break; 2833 } 2834 } 2835 2836 // Don't check all uppercase words 2837 if (!isUpper) { 2838 bool foundMatch = false; 2839 wordOffset = word - text; 2840 testWord.SetTo(word, wordLength); 2841 2842 testWord = testWord.ToLower(); 2843 DSPELL(printf("Testing: \"%s\"\n", testWord.String())); 2844 2845 int32 key = -1; 2846 if (gDictCount) 2847 key = gExactWords[0]->GetKey(testWord.String()); 2848 2849 // Search all dictionaries 2850 for (int32 i = 0; i < gDictCount; i++) { 2851 if (gExactWords[i]->Lookup(key) >= 0) { 2852 foundMatch = true; 2853 break; 2854 } 2855 } 2856 2857 if (!foundMatch) { 2858 if (flags & S_CLEAR_ERRORS) 2859 RemoveSpellMark(nextHighlight, wordOffset); 2860 2861 if (flags & S_SHOW_ERRORS) 2862 AddSpellMark(wordOffset, wordOffset + wordLength); 2863 } else if (flags & S_CLEAR_ERRORS) 2864 RemoveSpellMark(nextHighlight, wordOffset + wordLength); 2865 2866 nextHighlight = wordOffset + wordLength; 2867 } 2868 } 2869 // Reset state to looking for word 2870 word = NULL; 2871 wordLength = 0; 2872 } 2873 } 2874 2875 if (nextHighlight <= end 2876 && (flags & S_CLEAR_ERRORS) != 0 2877 && nextHighlight < TextLength()) 2878 SetFontAndColor(nextHighlight, end, NULL, B_FONT_ALL, &kNormalTextColor); 2879 } 2880 2881 2882 void 2883 TTextView::FindSpellBoundry(int32 length, int32 offset, int32 *_start, int32 *_end) 2884 { 2885 int32 start, end, textLength; 2886 const char *text = Text(); 2887 textLength = TextLength(); 2888 2889 for (start = offset - 1; start >= 0 2890 && (isalpha(text[start]) || text[start] == '\''); start--) {} 2891 2892 start++; 2893 2894 for (end = offset + length; end < textLength 2895 && (isalpha(text[end]) || text[end] == '\''); end++) {} 2896 2897 *_start = start; 2898 *_end = end; 2899 } 2900 2901 2902 TTextView::spell_mark * 2903 TTextView::FindSpellMark(int32 start, int32 end, spell_mark **_previousMark) 2904 { 2905 spell_mark *lastMark = NULL; 2906 2907 for (spell_mark *spellMark = fFirstSpellMark; spellMark; spellMark = spellMark->next) { 2908 if (spellMark->start < end && spellMark->end > start) { 2909 if (_previousMark) 2910 *_previousMark = lastMark; 2911 return spellMark; 2912 } 2913 2914 lastMark = spellMark; 2915 } 2916 return NULL; 2917 } 2918 2919 2920 void 2921 TTextView::UpdateSpellMarks(int32 offset, int32 length) 2922 { 2923 DSPELL(printf("UpdateSpellMarks: offset = %ld, length = %ld\n", offset, length)); 2924 2925 spell_mark *spellMark; 2926 for (spellMark = fFirstSpellMark; spellMark; spellMark = spellMark->next) { 2927 DSPELL(printf("\tfound: %ld - %ld\n", spellMark->start, spellMark->end)); 2928 2929 if (spellMark->end < offset) 2930 continue; 2931 2932 if (spellMark->start > offset) 2933 spellMark->start += length; 2934 2935 spellMark->end += length; 2936 2937 DSPELL(printf("\t-> reset: %ld - %ld\n", spellMark->start, spellMark->end)); 2938 } 2939 } 2940 2941 2942 status_t 2943 TTextView::AddSpellMark(int32 start, int32 end) 2944 { 2945 DSPELL(printf("AddSpellMark: start = %ld, end = %ld\n", start, end)); 2946 2947 // check if there is already a mark for this passage 2948 spell_mark *spellMark = FindSpellMark(start, end); 2949 if (spellMark) { 2950 if (spellMark->start == start && spellMark->end == end) { 2951 DSPELL(printf("\tfound one\n")); 2952 return B_OK; 2953 } 2954 2955 DSPELL(printf("\tremove old one\n")); 2956 RemoveSpellMark(start, end); 2957 } 2958 2959 spellMark = (spell_mark *)malloc(sizeof(spell_mark)); 2960 if (spellMark == NULL) 2961 return B_NO_MEMORY; 2962 2963 spellMark->start = start; 2964 spellMark->end = end; 2965 spellMark->style = RunArray(start, end); 2966 2967 // set the spell marks appearance 2968 BFont font(fFont); 2969 font.SetFace(B_BOLD_FACE | B_ITALIC_FACE); 2970 SetFontAndColor(start, end, &font, B_FONT_ALL, &kSpellTextColor); 2971 2972 // add it to the queue 2973 spellMark->next = fFirstSpellMark; 2974 fFirstSpellMark = spellMark; 2975 2976 return B_OK; 2977 } 2978 2979 2980 bool 2981 TTextView::RemoveSpellMark(int32 start, int32 end) 2982 { 2983 DSPELL(printf("RemoveSpellMark: start = %ld, end = %ld\n", start, end)); 2984 2985 // find spell mark 2986 spell_mark *lastMark = NULL; 2987 spell_mark *spellMark = FindSpellMark(start, end, &lastMark); 2988 if (spellMark == NULL) { 2989 DSPELL(printf("\tnot found!\n")); 2990 return false; 2991 } 2992 2993 DSPELL(printf("\tfound: %ld - %ld\n", spellMark->start, spellMark->end)); 2994 2995 // dequeue the spell mark 2996 if (lastMark) 2997 lastMark->next = spellMark->next; 2998 else 2999 fFirstSpellMark = spellMark->next; 3000 3001 if (spellMark->start < start) 3002 start = spellMark->start; 3003 if (spellMark->end > end) 3004 end = spellMark->end; 3005 3006 // reset old text run array 3007 SetRunArray(start, end, spellMark->style); 3008 3009 free(spellMark->style); 3010 free(spellMark); 3011 3012 return true; 3013 } 3014 3015 3016 void 3017 TTextView::RemoveSpellMarks() 3018 { 3019 spell_mark *spellMark, *nextMark; 3020 3021 for (spellMark = fFirstSpellMark; spellMark; spellMark = nextMark) { 3022 nextMark = spellMark->next; 3023 3024 // reset old text run array 3025 SetRunArray(spellMark->start, spellMark->end, spellMark->style); 3026 3027 free(spellMark->style); 3028 free(spellMark); 3029 } 3030 3031 fFirstSpellMark = NULL; 3032 } 3033 3034 3035 void 3036 TTextView::EnableSpellCheck(bool enable) 3037 { 3038 if (fSpellCheck == enable) 3039 return; 3040 3041 fSpellCheck = enable; 3042 int32 textLength = TextLength(); 3043 if (fSpellCheck) { 3044 // work-around for a bug in the BTextView class 3045 // which causes lots of flicker 3046 int32 start, end; 3047 GetSelection(&start, &end); 3048 if (start != end) 3049 Select(start, start); 3050 3051 CheckSpelling(0, textLength); 3052 3053 if (start != end) 3054 Select(start, end); 3055 } 3056 else 3057 RemoveSpellMarks(); 3058 } 3059 3060 3061 void 3062 TTextView::WindowActivated(bool flag) 3063 { 3064 if (!flag) { 3065 // WindowActivated(false) は、IM も Inactive になり、そのまま確定される。 3066 // しかしこの場合、input_server が B_INPUT_METHOD_EVENT(B_INPUT_METHOD_STOPPED) 3067 // を送ってこないまま矛盾してしまうので、やむを得ずここでつじつまあわせ処理している。 3068 // OpenBeOSで修正されることを願って暫定処置としている。 3069 fInputMethodUndoState.active = false; 3070 // fInputMethodUndoBufferに溜まっている最後のデータがK_INSERTEDなら(確定)正規のバッファへ追加 3071 if (fInputMethodUndoBuffer.CountItems() > 0) { 3072 KUndoItem *item = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1); 3073 if (item->History == K_INSERTED) { 3074 fUndoBuffer.MakeNewUndoItem(); 3075 fUndoBuffer.AddUndo(item->RedoText, item->Length, item->Offset, 3076 item->History, item->CursorPos); 3077 fUndoBuffer.MakeNewUndoItem(); 3078 } 3079 fInputMethodUndoBuffer.MakeEmpty(); 3080 } 3081 } 3082 BTextView::WindowActivated(flag); 3083 } 3084 3085 3086 void 3087 TTextView::AddQuote(int32 start, int32 finish) 3088 { 3089 BRect rect = Bounds(); 3090 3091 int32 lineStart; 3092 GoToLine(CurrentLine()); 3093 GetSelection(&lineStart, &lineStart); 3094 3095 // make sure that we're changing the whole last line, too 3096 int32 lineEnd = finish > lineStart ? finish - 1 : finish; 3097 { 3098 const char *text = Text(); 3099 while (text[lineEnd] && text[lineEnd] != '\n') 3100 lineEnd++; 3101 } 3102 Select(lineStart, lineEnd); 3103 3104 int32 textLength = lineEnd - lineStart; 3105 char *text = (char *)malloc(textLength + 1); 3106 if (text == NULL) 3107 return; 3108 3109 GetText(lineStart, textLength, text); 3110 3111 int32 quoteLength = strlen(QUOTE); 3112 int32 targetLength = 0; 3113 char *target = NULL; 3114 int32 lastLine = 0; 3115 3116 for (int32 index = 0; index < textLength; index++) { 3117 if (text[index] == '\n' || index == textLength - 1) { 3118 // add quote to this line 3119 int32 lineLength = index - lastLine + 1; 3120 3121 target = (char *)realloc(target, targetLength + lineLength + quoteLength); 3122 if (target == NULL) { 3123 // free the old buffer? 3124 free(text); 3125 return; 3126 } 3127 3128 // copy the quote sign 3129 memcpy(&target[targetLength], QUOTE, quoteLength); 3130 targetLength += quoteLength; 3131 3132 // copy the rest of the line 3133 memcpy(&target[targetLength], &text[lastLine], lineLength); 3134 targetLength += lineLength; 3135 3136 lastLine = index + 1; 3137 } 3138 } 3139 3140 // replace with quoted text 3141 free(text); 3142 Delete(); 3143 3144 if (fColoredQuotes) { 3145 const BFont *font = Font(); 3146 TextRunArray style(targetLength / 8 + 8); 3147 3148 FillInQuoteTextRuns(NULL, NULL, target, targetLength, font, 3149 &style.Array(), style.MaxEntries()); 3150 Insert(target, targetLength, &style.Array()); 3151 } else 3152 Insert(target, targetLength); 3153 3154 free(target); 3155 3156 // redo the old selection (compute the new start if necessary) 3157 Select(start + quoteLength, finish + (targetLength - textLength)); 3158 3159 ScrollTo(rect.LeftTop()); 3160 } 3161 3162 3163 void 3164 TTextView::RemoveQuote(int32 start, int32 finish) 3165 { 3166 BRect rect = Bounds(); 3167 3168 GoToLine(CurrentLine()); 3169 int32 lineStart; 3170 GetSelection(&lineStart, &lineStart); 3171 3172 // make sure that we're changing the whole last line, too 3173 int32 lineEnd = finish > lineStart ? finish - 1 : finish; 3174 const char *text = Text(); 3175 while (text[lineEnd] && text[lineEnd] != '\n') 3176 lineEnd++; 3177 3178 Select(lineStart, lineEnd); 3179 3180 int32 length = lineEnd - lineStart; 3181 char *target = (char *)malloc(length + 1); 3182 if (target == NULL) 3183 return; 3184 3185 int32 quoteLength = strlen(QUOTE); 3186 int32 removed = 0; 3187 text += lineStart; 3188 3189 for (int32 index = 0; index < length;) { 3190 // find out the length of the current line 3191 int32 lineLength = 0; 3192 while (index + lineLength < length && text[lineLength] != '\n') 3193 lineLength++; 3194 3195 // include the newline to be part of this line 3196 if (text[lineLength] == '\n' && index + lineLength + 1 < length) 3197 lineLength++; 3198 3199 if (!strncmp(text, QUOTE, quoteLength)) { 3200 // remove quote 3201 length -= quoteLength; 3202 removed += quoteLength; 3203 3204 lineLength -= quoteLength; 3205 text += quoteLength; 3206 } 3207 3208 if (lineLength == 0) { 3209 target[index] = '\0'; 3210 break; 3211 } 3212 3213 memcpy(&target[index], text, lineLength); 3214 3215 text += lineLength; 3216 index += lineLength; 3217 } 3218 3219 if (removed) { 3220 Delete(); 3221 3222 if (fColoredQuotes) { 3223 const BFont *font = Font(); 3224 TextRunArray style(length / 8 + 8); 3225 3226 FillInQuoteTextRuns(NULL, NULL, target, length, font, 3227 &style.Array(), style.MaxEntries()); 3228 Insert(target, length, &style.Array()); 3229 } else 3230 Insert(target, length); 3231 3232 // redo old selection 3233 bool noSelection = start == finish; 3234 3235 if (start > lineStart + quoteLength) 3236 start -= quoteLength; 3237 else 3238 start = lineStart; 3239 3240 if (noSelection) 3241 finish = start; 3242 else 3243 finish -= removed; 3244 } 3245 3246 free(target); 3247 3248 Select(start, finish); 3249 ScrollTo(rect.LeftTop()); 3250 } 3251 3252 3253 void 3254 TTextView::Undo(BClipboard */*clipboard*/) 3255 { 3256 if (fInputMethodUndoState.active) 3257 return; 3258 3259 int32 length, offset, cursorPos; 3260 undo_type history; 3261 char *text; 3262 status_t status; 3263 3264 status = fUndoBuffer.Undo(&text, &length, &offset, &history, &cursorPos); 3265 if (status == B_OK) { 3266 fUndoBuffer.Off(); 3267 3268 switch (history) { 3269 case K_INSERTED: 3270 BTextView::Delete(offset, offset + length); 3271 Select(offset, offset); 3272 break; 3273 3274 case K_DELETED: 3275 BTextView::Insert(offset, text, length); 3276 Select(offset, offset + length); 3277 break; 3278 3279 case K_REPLACED: 3280 BTextView::Delete(offset, offset + length); 3281 status = fUndoBuffer.Undo(&text, &length, &offset, &history, &cursorPos); 3282 if (status == B_OK && history == K_DELETED) { 3283 BTextView::Insert(offset, text, length); 3284 Select(offset, offset + length); 3285 } else { 3286 ::beep(); 3287 (new BAlert("", 3288 B_TRANSLATE("Inconsistency occurred in the undo/redo " 3289 "buffer."), 3290 B_TRANSLATE("OK")))->Go(); 3291 } 3292 break; 3293 } 3294 ScrollToSelection(); 3295 ContentChanged(); 3296 fUndoBuffer.On(); 3297 } 3298 } 3299 3300 3301 void 3302 TTextView::Redo() 3303 { 3304 if (fInputMethodUndoState.active) 3305 return; 3306 3307 int32 length, offset, cursorPos; 3308 undo_type history; 3309 char *text; 3310 status_t status; 3311 bool replaced; 3312 3313 status = fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced); 3314 if (status == B_OK) { 3315 fUndoBuffer.Off(); 3316 3317 switch (history) { 3318 case K_INSERTED: 3319 BTextView::Insert(offset, text, length); 3320 Select(offset, offset + length); 3321 break; 3322 3323 case K_DELETED: 3324 BTextView::Delete(offset, offset + length); 3325 if (replaced) { 3326 fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced); 3327 BTextView::Insert(offset, text, length); 3328 } 3329 Select(offset, offset + length); 3330 break; 3331 3332 case K_REPLACED: 3333 ::beep(); 3334 (new BAlert("", 3335 B_TRANSLATE("Inconsistency occurred in the undo/redo " 3336 "buffer."), 3337 B_TRANSLATE("OK")))->Go(); 3338 break; 3339 } 3340 ScrollToSelection(); 3341 ContentChanged(); 3342 fUndoBuffer.On(); 3343 } 3344 } 3345 3346