1 /* 2 Open Tracker License 3 4 Terms and Conditions 5 6 Copyright (c) 1991-2001, Be Incorporated. All rights reserved. 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy of 9 this software and associated documentation files (the "Software"), to deal in 10 the Software without restriction, including without limitation the rights to 11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 of the Software, and to permit persons to whom the Software is furnished to do 13 so, subject to the following conditions: 14 15 The above copyright notice and this permission notice applies to all licensees 16 and shall be included in all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION 23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 25 Except as contained in this notice, the name of Be Incorporated shall not be 26 used in advertising or otherwise to promote the sale, use or other dealings in 27 this Software without prior written authorization from Be Incorporated. 28 29 BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks 30 of Be Incorporated in the United States and other countries. Other brand product 31 names are registered trademarks or trademarks of their respective holders. 32 All rights reserved. 33 */ 34 35 36 #include <stdlib.h> 37 #include <string.h> 38 #include <stdio.h> 39 #include <ctype.h> 40 41 #include <Debug.h> 42 #include <Alert.h> 43 #include <Beep.h> 44 #include <E-mail.h> 45 #include <Beep.h> 46 #include <MenuItem.h> 47 #include <NodeInfo.h> 48 #include <NodeMonitor.h> 49 #include <Path.h> 50 #include <Mime.h> 51 #include <Beep.h> 52 #include <PopUpMenu.h> 53 #include <MenuItem.h> 54 #include <Region.h> 55 #include <Roster.h> 56 #include <ScrollView.h> 57 #include <TextView.h> 58 #include <UTF8.h> 59 #include <MenuItem.h> 60 #include <Roster.h> 61 #include <Clipboard.h> 62 #include <Input.h> 63 64 #include <MailMessage.h> 65 #include <MailAttachment.h> 66 #include <mail_util.h> 67 68 #include <MDRLanguage.h> 69 70 #include "MailApp.h" 71 #include "MailSupport.h" 72 #include "MailWindow.h" 73 #include "Messages.h" 74 #include "Content.h" 75 #include "Utilities.h" 76 #include "FieldMsg.h" 77 #include "Words.h" 78 79 80 #define DEBUG_SPELLCHECK 0 81 #if DEBUG_SPELLCHECK 82 # define DSPELL(x) x 83 #else 84 # define DSPELL(x) ; 85 #endif 86 87 const rgb_color kNormalTextColor = {0, 0, 0, 255}; 88 const rgb_color kSpellTextColor = {255, 0, 0, 255}; 89 const rgb_color kHyperLinkColor = {0, 0, 255, 255}; 90 const rgb_color kHeaderColor = {72, 72, 72, 255}; 91 92 const rgb_color kQuoteColors[] = { 93 {0, 0, 0x80, 0}, // 3rd, 6th, ... quote level color (blue) 94 {0, 0x80, 0, 0}, // 1st, 4th, ... quote level color (green) 95 {0x80, 0, 0, 0} // 2nd, ... (red) 96 }; 97 const int32 kNumQuoteColors = 3; 98 99 const rgb_color kDiffColors[] = { 100 {0xb0, 0, 0, 0}, // '-', red 101 {0, 0x90, 0, 0}, // '+', green 102 {0x6a, 0x6a, 0x6a, 0} // '@@', dark grey 103 }; 104 105 void Unicode2UTF8(int32 c, char **out); 106 107 108 inline bool 109 IsInitialUTF8Byte(uchar b) 110 { 111 return ((b & 0xC0) != 0x80); 112 } 113 114 115 void 116 Unicode2UTF8(int32 c, char **out) 117 { 118 char *s = *out; 119 120 ASSERT(c < 0x200000); 121 122 if (c < 0x80) 123 *(s++) = c; 124 else if (c < 0x800) 125 { 126 *(s++) = 0xc0 | (c >> 6); 127 *(s++) = 0x80 | (c & 0x3f); 128 } 129 else if (c < 0x10000) 130 { 131 *(s++) = 0xe0 | (c >> 12); 132 *(s++) = 0x80 | ((c >> 6) & 0x3f); 133 *(s++) = 0x80 | (c & 0x3f); 134 } 135 else if (c < 0x200000) 136 { 137 *(s++) = 0xf0 | (c >> 18); 138 *(s++) = 0x80 | ((c >> 12) & 0x3f); 139 *(s++) = 0x80 | ((c >> 6) & 0x3f); 140 *(s++) = 0x80 | (c & 0x3f); 141 } 142 *out = s; 143 } 144 145 146 static bool 147 FilterHTMLTag(int32 &first, char **t, char *end) 148 { 149 const char *newlineTags[] = { 150 "br", "/p", "/div", "/table", "/tr", 151 NULL}; 152 153 char *a = *t; 154 155 // check for some common entities (in ISO-Latin-1) 156 if (first == '&') { 157 // filter out and convert decimal values 158 if (a[1] == '#' && sscanf(a + 2, "%ld;", &first) == 1) { 159 t[0] += strchr(a, ';') - a; 160 return false; 161 } 162 163 const struct { const char *name; int32 code; } entities[] = { 164 // this list is sorted alphabetically to be binary searchable 165 // the current implementation doesn't do this, though 166 167 // "name" is the entity name, 168 // "code" is the corresponding unicode 169 {"AElig;", 0x00c6}, 170 {"Aacute;", 0x00c1}, 171 {"Acirc;", 0x00c2}, 172 {"Agrave;", 0x00c0}, 173 {"Aring;", 0x00c5}, 174 {"Atilde;", 0x00c3}, 175 {"Auml;", 0x00c4}, 176 {"Ccedil;", 0x00c7}, 177 {"Eacute;", 0x00c9}, 178 {"Ecirc;", 0x00ca}, 179 {"Egrave;", 0x00c8}, 180 {"Euml;", 0x00cb}, 181 {"Iacute;", 0x00cd}, 182 {"Icirc;", 0x00ce}, 183 {"Igrave;", 0x00cc}, 184 {"Iuml;", 0x00cf}, 185 {"Ntilde;", 0x00d1}, 186 {"Oacute;", 0x00d3}, 187 {"Ocirc;", 0x00d4}, 188 {"Ograve;", 0x00d2}, 189 {"Ouml;", 0x00d6}, 190 {"Uacute;", 0x00da}, 191 {"Ucirc;", 0x00db}, 192 {"Ugrave;", 0x00d9}, 193 {"Uuml;", 0x00dc}, 194 {"aacute;", 0x00e1}, 195 {"acirc;", 0x00e2}, 196 {"aelig;", 0x00e6}, 197 {"agrave;", 0x00e0}, 198 {"amp;", '&'}, 199 {"aring;", 0x00e5}, 200 {"atilde;", 0x00e3}, 201 {"auml;", 0x00e4}, 202 {"ccedil;", 0x00e7}, 203 {"copy;", 0x00a9}, 204 {"eacute;", 0x00e9}, 205 {"ecirc;", 0x00ea}, 206 {"egrave;", 0x00e8}, 207 {"euml;", 0x00eb}, 208 {"gt;", '>'}, 209 {"iacute;", 0x00ed}, 210 {"icirc;", 0x00ee}, 211 {"igrave;", 0x00ec}, 212 {"iuml;", 0x00ef}, 213 {"lt;", '<'}, 214 {"nbsp;", ' '}, 215 {"ntilde;", 0x00f1}, 216 {"oacute;", 0x00f3}, 217 {"ocirc;", 0x00f4}, 218 {"ograve;", 0x00f2}, 219 {"ouml;", 0x00f6}, 220 {"quot;", '"'}, 221 {"szlig;", 0x00f6}, 222 {"uacute;", 0x00fa}, 223 {"ucirc;", 0x00fb}, 224 {"ugrave;", 0x00f9}, 225 {"uuml;", 0x00fc}, 226 {NULL, 0} 227 }; 228 229 for (int32 i = 0; entities[i].name; i++) { 230 // entities are case-sensitive 231 int32 length = strlen(entities[i].name); 232 if (!strncmp(a + 1, entities[i].name, length)) { 233 t[0] += length; // note that the '&' is included here 234 first = entities[i].code; 235 return false; 236 } 237 } 238 } 239 240 // no tag to filter 241 if (first != '<') 242 return false; 243 244 a++; 245 246 // is the tag one of the newline tags? 247 248 bool newline = false; 249 for (int i = 0; newlineTags[i]; i++) { 250 int length = strlen(newlineTags[i]); 251 if (!strncasecmp(a, (char *)newlineTags[i], length) && !isalnum(a[length])) { 252 newline = true; 253 break; 254 } 255 } 256 257 // oh, it's not, so skip it! 258 259 if (!strncasecmp(a, "head", 4)) { // skip "head" completely 260 for (; a[0] && a < end; a++) { 261 // Find the end of the HEAD section, or the start of the BODY, 262 // which happens for some malformed spam. 263 if (strncasecmp (a, "</head", 6) == 0 || 264 strncasecmp (a, "<body", 5) == 0) 265 break; 266 } 267 } 268 269 // skip until tag end 270 while (a[0] && a[0] != '>' && a < end) 271 a++; 272 273 t[0] = a; 274 275 if (newline) { 276 first = '\n'; 277 return false; 278 } 279 280 return true; 281 } 282 283 284 /** Returns the type and length of the URL in the string if it is one. 285 * If the "url" string is specified, it will fill it with the complete 286 * URL that might differ from the one in the text itself (i.e. because 287 * of an prepended "http://"). 288 */ 289 290 static uint8 291 CheckForURL(const char *string, size_t &urlLength, BString *url = NULL) 292 { 293 const char *urlPrefixes[] = { 294 "http://", 295 "ftp://", 296 "shttp://", 297 "https://", 298 "finger://", 299 "telnet://", 300 "gopher://", 301 "news://", 302 "nntp://", 303 "file://", 304 NULL 305 }; 306 307 // 308 // Search for URL prefix 309 // 310 uint8 type = 0; 311 for (const char **prefix = urlPrefixes; *prefix != 0; prefix++) { 312 if (!cistrncmp(string, *prefix, strlen(*prefix))) { 313 type = TYPE_URL; 314 break; 315 } 316 } 317 318 // 319 // Not a URL? check for "mailto:" or "www." 320 // 321 if (type == 0 && !cistrncmp(string, "mailto:", 7)) 322 type = TYPE_MAILTO; 323 if (type == 0 && !strncmp(string, "www.", 4)) { 324 // this type will be special cased later (and a http:// is added 325 // for the enclosure address) 326 type = TYPE_URL; 327 } 328 if (type == 0) { 329 // check for valid eMail addresses 330 const char *at = strchr(string, '@'); 331 if (at) { 332 const char *pos = string; 333 bool readName = false; 334 for (; pos < at; pos++) { 335 // ToDo: are these all allowed characters? 336 if (!isalnum(pos[0]) && pos[0] != '_' && pos[0] != '.' && pos[0] != '-') 337 break; 338 339 readName = true; 340 } 341 if (pos == at && readName) 342 type = TYPE_MAILTO; 343 } 344 } 345 346 if (type == 0) 347 return 0; 348 349 int32 index = strcspn(string, " \t<>)\"\\,\r\n"); 350 351 // filter out some punctuation marks if they are the last character 352 char suffix = string[index - 1]; 353 if (suffix == '.' 354 || suffix == ',' 355 || suffix == '?' 356 || suffix == '!' 357 || suffix == ':' 358 || suffix == ';') 359 index--; 360 361 if (type == TYPE_URL) { 362 char *parenthesis = NULL; 363 364 // filter out a trailing ')' if there is no left parenthesis before 365 if (string[index - 1] == ')') { 366 char *parenthesis = strchr(string, '('); 367 if (parenthesis == NULL || parenthesis > string + index) 368 index--; 369 } 370 371 // filter out a trailing ']' if there is no left bracket before 372 if (parenthesis == NULL && string[index - 1] == ']') { 373 char *parenthesis = strchr(string, '['); 374 if (parenthesis == NULL || parenthesis > string + index) 375 index--; 376 } 377 } 378 379 if (url != NULL) { 380 // copy the address to the specified string 381 if (type == TYPE_URL && string[0] == 'w') { 382 // URL starts with "www.", so add the protocol to it 383 url->SetTo("http://"); 384 url->Append(string, index); 385 } else if (type == TYPE_MAILTO && cistrncmp(string, "mailto:", 7)) { 386 // eMail address had no "mailto:" prefix 387 url->SetTo("mailto:"); 388 url->Append(string, index); 389 } else 390 url->SetTo(string, index); 391 } 392 urlLength = index; 393 394 return type; 395 } 396 397 398 static void 399 CopyQuotes(const char *text, size_t length, char *outText, size_t &outLength) 400 { 401 // count qoute level (to be able to wrap quotes correctly) 402 403 const char *quote = QUOTE; 404 int32 level = 0; 405 for (size_t i = 0; i < length; i++) { 406 if (text[i] == quote[0]) 407 level++; 408 else if (text[i] != ' ' && text[i] != '\t') 409 break; 410 } 411 412 // if there are too much quotes, try to preserve the quote color level 413 if (level > 10) 414 level = kNumQuoteColors * 3 + (level % kNumQuoteColors); 415 416 // copy the quotes to outText 417 418 const int32 quoteLength = strlen(QUOTE); 419 outLength = 0; 420 while (level-- > 0) { 421 strcpy(outText + outLength, QUOTE); 422 outLength += quoteLength; 423 } 424 } 425 426 427 int32 428 diff_mode(char c) 429 { 430 if (c == '+') 431 return 2; 432 if (c == '-') 433 return 1; 434 if (c == '@') 435 return 3; 436 if (c == ' ') 437 return 0; 438 439 // everything else ends the diff mode 440 return -1; 441 } 442 443 444 bool 445 is_quote_char(char c) 446 { 447 return c == '>' || c == '|'; 448 } 449 450 451 /*! Fills the specified text_run_array with the correct values for the 452 specified text. 453 If "view" is NULL, it will assume that "line" lies on a line break, 454 if not, it will correctly retrieve the number of quotes the current 455 line already has. 456 */ 457 void 458 FillInQuoteTextRuns(BTextView* view, quote_context* context, const char* line, 459 int32 length, const BFont& font, text_run_array* style, int32 maxStyles) 460 { 461 text_run* runs = style->runs; 462 int32 index = style->count; 463 bool begin; 464 int32 pos = 0; 465 int32 diffMode = 0; 466 bool inDiff = false; 467 bool wasDiff = false; 468 int32 level = 0; 469 470 // get index to the beginning of the current line 471 472 if (context != NULL) { 473 level = context->level; 474 diffMode = context->diff_mode; 475 begin = context->begin; 476 inDiff = context->in_diff; 477 wasDiff = context->was_diff; 478 } else if (view != NULL) { 479 int32 start, end; 480 view->GetSelection(&end, &end); 481 482 begin = view->TextLength() == 0 483 || view->ByteAt(view->TextLength() - 1) == '\n'; 484 485 // the following line works only reliable when text wrapping is set to 486 // off; so the complicated version actually used here is necessary: 487 // start = view->OffsetAt(view->CurrentLine()); 488 489 const char *text = view->Text(); 490 491 if (!begin) { 492 // if the text is not the start of a new line, go back 493 // to the first character in the current line 494 for (start = end; start > 0; start--) { 495 if (text[start - 1] == '\n') 496 break; 497 } 498 } 499 500 // get number of nested qoutes for current line 501 502 if (!begin && start < end) { 503 begin = true; 504 // if there was no text in this line, there may come 505 // more nested quotes 506 507 diffMode = diff_mode(text[start]); 508 if (diffMode == 0) { 509 for (int32 i = start; i < end; i++) { 510 if (is_quote_char(text[i])) 511 level++; 512 else if (text[i] != ' ' && text[i] != '\t') { 513 begin = false; 514 break; 515 } 516 } 517 } else 518 inDiff = true; 519 520 if (begin) { 521 // skip leading spaces (tabs & newlines aren't allowed here) 522 while (line[pos] == ' ') 523 pos++; 524 } 525 } 526 } else 527 begin = true; 528 529 // set styles for all qoute levels in the text to be inserted 530 531 for (int32 pos = 0; pos < length;) { 532 int32 next; 533 if (begin && is_quote_char(line[pos])) { 534 begin = false; 535 536 while (pos < length && line[pos] != '\n') { 537 // insert style for each quote level 538 level++; 539 540 bool search = true; 541 for (next = pos + 1; next < length; next++) { 542 if ((search && is_quote_char(line[next])) 543 || line[next] == '\n') 544 break; 545 else if (search && line[next] != ' ' && line[next] != '\t') 546 search = false; 547 } 548 549 runs[index].offset = pos; 550 runs[index].font = font; 551 runs[index].color = level > 0 552 ? kQuoteColors[level % kNumQuoteColors] : kNormalTextColor; 553 554 pos = next; 555 if (++index >= maxStyles) 556 break; 557 } 558 } else { 559 if (begin) { 560 if (!inDiff) { 561 inDiff = !strncmp(&line[pos], "--- ", 4); 562 wasDiff = false; 563 } 564 if (inDiff) { 565 diffMode = diff_mode(line[pos]); 566 if (diffMode < 0) { 567 inDiff = false; 568 wasDiff = true; 569 } 570 } 571 } 572 573 runs[index].offset = pos; 574 runs[index].font = font; 575 if (wasDiff) 576 runs[index].color = kDiffColors[diff_mode('@') - 1]; 577 else if (diffMode <= 0) { 578 runs[index].color = level > 0 579 ? kQuoteColors[level % kNumQuoteColors] : kNormalTextColor; 580 } else 581 runs[index].color = kDiffColors[diffMode - 1]; 582 583 begin = false; 584 585 for (next = pos; next < length; next++) { 586 if (line[next] == '\n') { 587 begin = true; 588 wasDiff = false; 589 break; 590 } 591 } 592 593 pos = next; 594 index++; 595 } 596 597 if (pos < length) 598 begin = line[pos] == '\n'; 599 600 if (begin) { 601 pos++; 602 level = 0; 603 wasDiff = false; 604 605 // skip one leading space (tabs & newlines aren't allowed here) 606 if (!inDiff && pos < length && line[pos] == ' ') 607 pos++; 608 } 609 610 if (index >= maxStyles) 611 break; 612 } 613 style->count = index; 614 615 if (context) { 616 // update context for next run 617 context->level = level; 618 context->diff_mode = diffMode; 619 context->begin = begin; 620 context->in_diff = inDiff; 621 context->was_diff = wasDiff; 622 } 623 } 624 625 626 // #pragma mark - 627 628 629 TextRunArray::TextRunArray(size_t entries) 630 : 631 fNumEntries(entries) 632 { 633 fArray = (text_run_array *)malloc(sizeof(int32) + sizeof(text_run) * entries); 634 if (fArray != NULL) 635 fArray->count = 0; 636 } 637 638 639 TextRunArray::~TextRunArray() 640 { 641 free(fArray); 642 } 643 644 645 //==================================================================== 646 // #pragma mark - 647 648 649 TContentView::TContentView(BRect rect, bool incoming, BEmailMessage *mail, 650 BFont* font, bool showHeader, bool coloredQuotes) 651 : BView(rect, "m_content", B_FOLLOW_ALL, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE), 652 fFocus(false), 653 fIncoming(incoming) 654 { 655 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 656 fOffset = 12; 657 658 BRect r(rect); 659 r.OffsetTo(0, 0); 660 r.right -= B_V_SCROLL_BAR_WIDTH; 661 r.bottom -= B_H_SCROLL_BAR_HEIGHT; 662 r.top += 4; 663 BRect text(r); 664 text.OffsetTo(0, 0); 665 text.InsetBy(5, 5); 666 667 fTextView = new TTextView(r, text, fIncoming, mail, this, font, 668 showHeader, coloredQuotes); 669 BScrollView *scroll = new BScrollView("", fTextView, B_FOLLOW_ALL, 0, true, true); 670 AddChild(scroll); 671 } 672 673 674 void 675 TContentView::MessageReceived(BMessage *msg) 676 { 677 switch (msg->what) { 678 case CHANGE_FONT: 679 { 680 BFont *font; 681 msg->FindPointer("font", (void **)&font); 682 fTextView->SetFontAndColor(0, LONG_MAX, font); 683 fTextView->Invalidate(Bounds()); 684 break; 685 } 686 687 case M_QUOTE: 688 { 689 int32 start, finish; 690 fTextView->GetSelection(&start, &finish); 691 fTextView->AddQuote(start, finish); 692 break; 693 } 694 case M_REMOVE_QUOTE: 695 { 696 int32 start, finish; 697 fTextView->GetSelection(&start, &finish); 698 fTextView->RemoveQuote(start, finish); 699 break; 700 } 701 702 case M_SIGNATURE: 703 { 704 entry_ref ref; 705 msg->FindRef("ref", &ref); 706 707 BFile file(&ref, B_READ_ONLY); 708 if (file.InitCheck() == B_OK) { 709 int32 start, finish; 710 fTextView->GetSelection(&start, &finish); 711 712 off_t size; 713 file.GetSize(&size); 714 if (size > 32768) // safety against corrupt signatures 715 break; 716 717 char *signature = (char *)malloc(size); 718 ssize_t bytesRead = file.Read(signature, size); 719 if (bytesRead < B_OK) { 720 free (signature); 721 break; 722 } 723 724 const char *text = fTextView->Text(); 725 int32 length = fTextView->TextLength(); 726 727 if (length && text[length - 1] != '\n') { 728 fTextView->Select(length, length); 729 730 char newLine = '\n'; 731 fTextView->Insert(&newLine, 1); 732 733 length++; 734 } 735 736 fTextView->Select(length, length); 737 fTextView->Insert(signature, bytesRead); 738 fTextView->Select(length, length + bytesRead); 739 fTextView->ScrollToSelection(); 740 741 fTextView->Select(start, finish); 742 fTextView->ScrollToSelection(); 743 free (signature); 744 } else { 745 beep(); 746 (new BAlert("", 747 MDR_DIALECT_CHOICE ("An error occurred trying to open this signature.", 748 "この署名を開くときにエラーが発生しました"), 749 MDR_DIALECT_CHOICE ("Sorry", "了解")))->Go(); 750 } 751 break; 752 } 753 754 case M_FIND: 755 FindString(msg->FindString("findthis")); 756 break; 757 758 default: 759 BView::MessageReceived(msg); 760 } 761 } 762 763 764 void 765 TContentView::FindString(const char *str) 766 { 767 int32 finish; 768 int32 pass = 0; 769 int32 start = 0; 770 771 if (str == NULL) 772 return; 773 774 // 775 // Start from current selection or from the beginning of the pool 776 // 777 const char *text = fTextView->Text(); 778 int32 count = fTextView->TextLength(); 779 fTextView->GetSelection(&start, &finish); 780 if (start != finish) 781 start = finish; 782 if (!count || text == NULL) 783 return; 784 785 // 786 // Do the find 787 // 788 while (pass < 2) { 789 long found = -1; 790 char lc = tolower(str[0]); 791 char uc = toupper(str[0]); 792 for (long i = start; i < count; i++) { 793 if (text[i] == lc || text[i] == uc) { 794 const char *s = str; 795 const char *t = text + i; 796 while (*s && (tolower(*s) == tolower(*t))) { 797 s++; 798 t++; 799 } 800 if (*s == 0) { 801 found = i; 802 break; 803 } 804 } 805 } 806 807 // 808 // Select the text if it worked 809 // 810 if (found != -1) { 811 Window()->Activate(); 812 fTextView->Select(found, found + strlen(str)); 813 fTextView->ScrollToSelection(); 814 fTextView->MakeFocus(true); 815 return; 816 } 817 else if (start) { 818 start = 0; 819 text = fTextView->Text(); 820 count = fTextView->TextLength(); 821 pass++; 822 } else { 823 beep(); 824 return; 825 } 826 } 827 } 828 829 830 void 831 TContentView::Focus(bool focus) 832 { 833 if (fFocus != focus) { 834 fFocus = focus; 835 Draw(Frame()); 836 } 837 } 838 839 840 void 841 TContentView::FrameResized(float /* width */, float /* height */) 842 { 843 BRect r(fTextView->Bounds()); 844 r.OffsetTo(0, 0); 845 r.InsetBy(5, 5); 846 fTextView->SetTextRect(r); 847 } 848 849 850 //==================================================================== 851 // #pragma mark - 852 853 854 TTextView::TTextView(BRect frame, BRect text, bool incoming, 855 BEmailMessage *mail, TContentView *view, BFont *font, 856 bool showHeader, bool coloredQuotes) 857 : BTextView(frame, "", text, B_FOLLOW_ALL, B_WILL_DRAW | B_NAVIGABLE), 858 fHeader(showHeader), 859 fColoredQuotes(coloredQuotes), 860 fReady(false), 861 fYankBuffer(NULL), 862 fLastPosition(-1), 863 fMail(mail), 864 fFont(font), 865 fParent(view), 866 fStopLoading(false), 867 fThread(0), 868 fPanel(NULL), 869 fIncoming(incoming), 870 fSpellCheck(false), 871 fRaw(false), 872 fCursor(false), 873 fFirstSpellMark(NULL) 874 { 875 BFont menuFont = *be_plain_font; 876 menuFont.SetSize(10); 877 878 fStopSem = create_sem(1, "reader_sem"); 879 SetStylable(true); 880 881 fEnclosures = new BList(); 882 883 // 884 // Enclosure pop up menu 885 // 886 fEnclosureMenu = new BPopUpMenu("Enclosure", false, false); 887 fEnclosureMenu->SetFont(&menuFont); 888 fEnclosureMenu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Save Enclosure", "添付ファイルを保存") B_UTF8_ELLIPSIS,new BMessage(M_SAVE))); 889 fEnclosureMenu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Open Enclosure", "添付ファイルを開く"), new BMessage(M_OPEN))); 890 891 // 892 // Hyperlink pop up menu 893 // 894 fLinkMenu = new BPopUpMenu("Link", false, false); 895 fLinkMenu->SetFont(&menuFont); 896 fLinkMenu->AddItem(new BMenuItem( 897 MDR_DIALECT_CHOICE ("Open This Link", "リンク先を開く"), 898 new BMessage(M_OPEN))); 899 fLinkMenu->AddItem(new BMenuItem( 900 MDR_DIALECT_CHOICE ("Copy Link Location", "リンク先をコピー"), 901 new BMessage(M_COPY))); 902 903 SetDoesUndo(true); 904 905 //Undo function 906 fUndoBuffer.On(); 907 fInputMethodUndoBuffer.On(); 908 fUndoState.replaced = false; 909 fUndoState.deleted = false; 910 fInputMethodUndoState.active = false; 911 fInputMethodUndoState.replace = false; 912 } 913 914 915 TTextView::~TTextView() 916 { 917 ClearList(); 918 delete fPanel; 919 920 if (fYankBuffer) 921 free(fYankBuffer); 922 923 delete_sem(fStopSem); 924 } 925 926 927 void 928 TTextView::AttachedToWindow() 929 { 930 BTextView::AttachedToWindow(); 931 fFont.SetSpacing(B_FIXED_SPACING); 932 SetFontAndColor(&fFont); 933 934 if (fMail != NULL) { 935 LoadMessage(fMail, false, NULL); 936 if (fIncoming) 937 MakeEditable(false); 938 } 939 } 940 941 942 void 943 TTextView::KeyDown(const char *key, int32 count) 944 { 945 char raw; 946 int32 end; 947 int32 start; 948 uint32 mods; 949 BMessage *msg; 950 int32 textLen = TextLength(); 951 952 msg = Window()->CurrentMessage(); 953 mods = msg->FindInt32("modifiers"); 954 955 switch (key[0]) 956 { 957 case B_HOME: 958 if (IsSelectable()) 959 { 960 if (IsEditable()) 961 BTextView::KeyDown(key, count); 962 else 963 { 964 // scroll to the beginning 965 Select(0, 0); 966 ScrollToSelection(); 967 } 968 } 969 break; 970 971 case B_END: 972 if (IsSelectable()) 973 { 974 if (IsEditable()) 975 BTextView::KeyDown(key, count); 976 else 977 { 978 // scroll to the end 979 int32 length = TextLength(); 980 Select(length, length); 981 ScrollToSelection(); 982 } 983 } 984 break; 985 986 case 0x02: // ^b - back 1 char 987 if (IsSelectable()) 988 { 989 GetSelection(&start, &end); 990 while (!IsInitialUTF8Byte(ByteAt(--start))) 991 { 992 if (start < 0) 993 { 994 start = 0; 995 break; 996 } 997 } 998 if (start >= 0) 999 { 1000 Select(start, start); 1001 ScrollToSelection(); 1002 } 1003 } 1004 break; 1005 1006 case B_DELETE: 1007 if (IsSelectable()) 1008 { 1009 if ((key[0] == B_DELETE) || (mods & B_CONTROL_KEY)) // ^d 1010 { 1011 if (IsEditable()) 1012 { 1013 GetSelection(&start, &end); 1014 if (start != end) 1015 Delete(); 1016 else 1017 { 1018 for (end = start + 1; !IsInitialUTF8Byte(ByteAt(end)); end++) 1019 { 1020 if (end > textLen) 1021 { 1022 end = textLen; 1023 break; 1024 } 1025 } 1026 Select(start, end); 1027 Delete(); 1028 } 1029 } 1030 } 1031 else 1032 Select(textLen, textLen); 1033 ScrollToSelection(); 1034 } 1035 break; 1036 1037 case 0x05: // ^e - end of line 1038 if ((IsSelectable()) && (mods & B_CONTROL_KEY)) 1039 { 1040 if (CurrentLine() == CountLines() - 1) 1041 Select(TextLength(), TextLength()); 1042 else 1043 { 1044 GoToLine(CurrentLine() + 1); 1045 GetSelection(&start, &end); 1046 Select(start - 1, start - 1); 1047 } 1048 } 1049 break; 1050 1051 case 0x06: // ^f - forward 1 char 1052 if (IsSelectable()) 1053 { 1054 GetSelection(&start, &end); 1055 if (end > start) 1056 start = end; 1057 else 1058 { 1059 for (end = start + 1; !IsInitialUTF8Byte(ByteAt(end)); end++) 1060 { 1061 if (end > textLen) 1062 { 1063 end = textLen; 1064 break; 1065 } 1066 } 1067 start = end; 1068 } 1069 Select(start, start); 1070 ScrollToSelection(); 1071 } 1072 break; 1073 1074 case 0x0e: // ^n - next line 1075 if (IsSelectable()) 1076 { 1077 raw = B_DOWN_ARROW; 1078 BTextView::KeyDown(&raw, 1); 1079 } 1080 break; 1081 1082 case 0x0f: // ^o - open line 1083 if (IsEditable()) 1084 { 1085 GetSelection(&start, &end); 1086 Delete(); 1087 1088 char newLine = '\n'; 1089 Insert(&newLine, 1); 1090 Select(start, start); 1091 ScrollToSelection(); 1092 } 1093 break; 1094 1095 case B_PAGE_UP: 1096 if (mods & B_CONTROL_KEY) { // ^k kill text from cursor to e-o-line 1097 if (IsEditable()) { 1098 GetSelection(&start, &end); 1099 if ((start != fLastPosition) && (fYankBuffer)) { 1100 free(fYankBuffer); 1101 fYankBuffer = NULL; 1102 } 1103 fLastPosition = start; 1104 if (CurrentLine() < (CountLines() - 1)) { 1105 GoToLine(CurrentLine() + 1); 1106 GetSelection(&end, &end); 1107 end--; 1108 } 1109 else 1110 end = TextLength(); 1111 if (end < start) 1112 break; 1113 if (start == end) 1114 end++; 1115 Select(start, end); 1116 if (fYankBuffer) { 1117 fYankBuffer = (char *)realloc(fYankBuffer, 1118 strlen(fYankBuffer) + (end - start) + 1); 1119 GetText(start, end - start, 1120 &fYankBuffer[strlen(fYankBuffer)]); 1121 } else { 1122 fYankBuffer = (char *)malloc(end - start + 1); 1123 GetText(start, end - start, fYankBuffer); 1124 } 1125 Delete(); 1126 ScrollToSelection(); 1127 } 1128 break; 1129 } 1130 1131 BTextView::KeyDown(key, count); 1132 break; 1133 1134 case 0x10: // ^p goto previous line 1135 if (IsSelectable()) { 1136 raw = B_UP_ARROW; 1137 BTextView::KeyDown(&raw, 1); 1138 } 1139 break; 1140 1141 case 0x19: // ^y yank text 1142 if ((IsEditable()) && (fYankBuffer)) { 1143 Delete(); 1144 Insert(fYankBuffer); 1145 ScrollToSelection(); 1146 } 1147 break; 1148 1149 default: 1150 BTextView::KeyDown(key, count); 1151 } 1152 } 1153 1154 1155 void 1156 TTextView::MakeFocus(bool focus) 1157 { 1158 if (!focus) { 1159 // ToDo: can someone please translate this? Otherwise I will remove it - axeld. 1160 // MakeFocus(false) は、IM も Inactive になり、そのまま確定される。 1161 // しかしこの場合、input_server が B_INPUT_METHOD_EVENT(B_INPUT_METHOD_STOPPED) 1162 // を送ってこないまま矛盾してしまうので、やむを得ずここでつじつまあわせ処理している。 1163 fInputMethodUndoState.active = false; 1164 // fInputMethodUndoBufferに溜まっている最後のデータがK_INSERTEDなら(確定)正規のバッファへ追加 1165 if (fInputMethodUndoBuffer.CountItems() > 0) { 1166 KUndoItem *item = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1); 1167 if (item->History == K_INSERTED) { 1168 fUndoBuffer.MakeNewUndoItem(); 1169 fUndoBuffer.AddUndo(item->RedoText, item->Length, item->Offset, item->History, item->CursorPos); 1170 fUndoBuffer.MakeNewUndoItem(); 1171 } 1172 fInputMethodUndoBuffer.MakeEmpty(); 1173 } 1174 } 1175 BTextView::MakeFocus(focus); 1176 1177 fParent->Focus(focus); 1178 } 1179 1180 1181 void 1182 TTextView::MessageReceived(BMessage *msg) 1183 { 1184 switch (msg->what) { 1185 case B_SIMPLE_DATA: 1186 { 1187 if (fIncoming) 1188 break; 1189 1190 BMessage message(REFS_RECEIVED); 1191 bool isEnclosure = false; 1192 bool inserted = false; 1193 1194 off_t len = 0; 1195 int32 end; 1196 int32 start; 1197 1198 int32 index = 0; 1199 entry_ref ref; 1200 while (msg->FindRef("refs", index++, &ref) == B_OK) { 1201 BFile file(&ref, B_READ_ONLY); 1202 if (file.InitCheck() == B_OK) { 1203 BNodeInfo node(&file); 1204 char type[B_FILE_NAME_LENGTH]; 1205 node.GetType(type); 1206 1207 off_t size = 0; 1208 file.GetSize(&size); 1209 1210 if (!strncasecmp(type, "text/", 5) && size > 0) { 1211 len += size; 1212 char *text = (char *)malloc(size); 1213 if (text == NULL) { 1214 puts("no memory!"); 1215 return; 1216 } 1217 if (file.Read(text, size) < B_OK) { 1218 puts("could not read from file"); 1219 free(text); 1220 continue; 1221 } 1222 if (!inserted) { 1223 GetSelection(&start, &end); 1224 Delete(); 1225 inserted = true; 1226 } 1227 1228 int32 offset = 0; 1229 for (int32 loop = 0; loop < size; loop++) { 1230 if (text[loop] == '\n') { 1231 Insert(&text[offset], loop - offset + 1); 1232 offset = loop + 1; 1233 } else if (text[loop] == '\r') { 1234 text[loop] = '\n'; 1235 Insert(&text[offset], loop - offset + 1); 1236 if ((loop + 1 < size) 1237 && (text[loop + 1] == '\n')) 1238 loop++; 1239 offset = loop + 1; 1240 } 1241 } 1242 free(text); 1243 } else { 1244 isEnclosure = true; 1245 message.AddRef("refs", &ref); 1246 } 1247 } 1248 } 1249 1250 if (index == 1) { 1251 // message doesn't contain any refs - maybe the parent class likes it 1252 BTextView::MessageReceived(msg); 1253 break; 1254 } 1255 1256 if (inserted) 1257 Select(start, start + len); 1258 if (isEnclosure) 1259 Window()->PostMessage(&message, Window()); 1260 break; 1261 } 1262 1263 case M_HEADER: 1264 msg->FindBool("header", &fHeader); 1265 SetText(NULL); 1266 LoadMessage(fMail, false, NULL); 1267 break; 1268 1269 case M_RAW: 1270 StopLoad(); 1271 1272 msg->FindBool("raw", &fRaw); 1273 SetText(NULL); 1274 LoadMessage(fMail, false, NULL); 1275 break; 1276 1277 case M_SELECT: 1278 if (IsSelectable()) 1279 Select(0, TextLength()); 1280 break; 1281 1282 case M_SAVE: 1283 Save(msg); 1284 break; 1285 1286 case B_NODE_MONITOR: 1287 { 1288 int32 opcode; 1289 if (msg->FindInt32("opcode", &opcode) == B_NO_ERROR) { 1290 dev_t device; 1291 if (msg->FindInt32("device", &device) < B_OK) 1292 break; 1293 ino_t inode; 1294 if (msg->FindInt64("node", &inode) < B_OK) 1295 break; 1296 1297 hyper_text *enclosure; 1298 for (int32 index = 0; 1299 (enclosure = (hyper_text *)fEnclosures->ItemAt(index++)) != NULL;) { 1300 if (device == enclosure->node.device 1301 && inode == enclosure->node.node) { 1302 if (opcode == B_ENTRY_REMOVED) { 1303 enclosure->saved = false; 1304 enclosure->have_ref = false; 1305 } else if (opcode == B_ENTRY_MOVED) { 1306 enclosure->ref.device = device; 1307 msg->FindInt64("to directory", &enclosure->ref.directory); 1308 1309 const char *name; 1310 msg->FindString("name", &name); 1311 enclosure->ref.set_name(name); 1312 } 1313 break; 1314 } 1315 } 1316 } 1317 break; 1318 } 1319 1320 // 1321 // Tracker has responded to a BMessage that was dragged out of 1322 // this email message. It has created a file for us, we just have to 1323 // put the stuff in it. 1324 // 1325 case B_COPY_TARGET: 1326 { 1327 BMessage data; 1328 if (msg->FindMessage("be:originator-data", &data) == B_OK) { 1329 entry_ref directory; 1330 const char *name; 1331 hyper_text *enclosure; 1332 1333 if (data.FindPointer("enclosure", (void **)&enclosure) == B_OK 1334 && msg->FindString("name", &name) == B_OK 1335 && msg->FindRef("directory", &directory) == B_OK) { 1336 switch (enclosure->type) { 1337 case TYPE_ENCLOSURE: 1338 case TYPE_BE_ENCLOSURE: 1339 { 1340 // 1341 // Enclosure. Decode the data and write it out. 1342 // 1343 BMessage saveMsg(M_SAVE); 1344 saveMsg.AddString("name", name); 1345 saveMsg.AddRef("directory", &directory); 1346 saveMsg.AddPointer("enclosure", enclosure); 1347 Save(&saveMsg, false); 1348 break; 1349 } 1350 1351 case TYPE_URL: 1352 { 1353 const char *replyType; 1354 if (msg->FindString("be:filetypes", &replyType) != B_OK) 1355 // drag recipient didn't ask for any specific type, 1356 // create a bookmark file as default 1357 replyType = "application/x-vnd.Be-bookmark"; 1358 1359 BDirectory dir(&directory); 1360 BFile file(&dir, name, B_READ_WRITE); 1361 if (file.InitCheck() == B_OK) { 1362 if (strcmp(replyType, "application/x-vnd.Be-bookmark") == 0) { 1363 // we got a request to create a bookmark, stuff 1364 // it with the url attribute 1365 file.WriteAttr("META:url", B_STRING_TYPE, 0, 1366 enclosure->name, strlen(enclosure->name) + 1); 1367 } else if (strcasecmp(replyType, "text/plain") == 0) { 1368 // create a plain text file, stuff it with 1369 // the url as text 1370 file.Write(enclosure->name, strlen(enclosure->name)); 1371 } 1372 1373 BNodeInfo fileInfo(&file); 1374 fileInfo.SetType(replyType); 1375 } 1376 break; 1377 } 1378 1379 case TYPE_MAILTO: 1380 { 1381 // 1382 // Add some attributes to the already created 1383 // person file. Strip out the 'mailto:' if 1384 // possible. 1385 // 1386 char *addrStart = enclosure->name; 1387 while (true) { 1388 if (*addrStart == ':') { 1389 addrStart++; 1390 break; 1391 } 1392 1393 if (*addrStart == '\0') { 1394 addrStart = enclosure->name; 1395 break; 1396 } 1397 1398 addrStart++; 1399 } 1400 1401 const char *replyType; 1402 if (msg->FindString("be:filetypes", &replyType) != B_OK) 1403 // drag recipient didn't ask for any specific type, 1404 // create a bookmark file as default 1405 replyType = "application/x-vnd.Be-bookmark"; 1406 1407 BDirectory dir(&directory); 1408 BFile file(&dir, name, B_READ_WRITE); 1409 if (file.InitCheck() == B_OK) { 1410 if (!strcmp(replyType, "application/x-person")) { 1411 // we got a request to create a bookmark, stuff 1412 // it with the address attribute 1413 file.WriteAttr("META:email", B_STRING_TYPE, 0, 1414 addrStart, strlen(enclosure->name) + 1); 1415 } else if (!strcasecmp(replyType, "text/plain")) { 1416 // create a plain text file, stuff it with the 1417 // email as text 1418 file.Write(addrStart, strlen(addrStart)); 1419 } 1420 1421 BNodeInfo fileInfo(&file); 1422 fileInfo.SetType(replyType); 1423 } 1424 break; 1425 } 1426 } 1427 } else { 1428 // 1429 // Assume this is handled by BTextView... 1430 // (Probably drag clipping.) 1431 // 1432 BTextView::MessageReceived(msg); 1433 } 1434 } 1435 break; 1436 } 1437 1438 case B_INPUT_METHOD_EVENT: 1439 { 1440 int32 im_op; 1441 if (msg->FindInt32("be:opcode", &im_op) == B_OK){ 1442 switch (im_op) { 1443 case B_INPUT_METHOD_STARTED: 1444 fInputMethodUndoState.replace = true; 1445 fInputMethodUndoState.active = true; 1446 break; 1447 case B_INPUT_METHOD_STOPPED: 1448 fInputMethodUndoState.active = false; 1449 if (fInputMethodUndoBuffer.CountItems() > 0) { 1450 KUndoItem *undo = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1); 1451 if (undo->History == K_INSERTED){ 1452 fUndoBuffer.MakeNewUndoItem(); 1453 fUndoBuffer.AddUndo(undo->RedoText, undo->Length, 1454 undo->Offset, undo->History, undo->CursorPos); 1455 fUndoBuffer.MakeNewUndoItem(); 1456 } 1457 fInputMethodUndoBuffer.MakeEmpty(); 1458 } 1459 break; 1460 case B_INPUT_METHOD_CHANGED: 1461 fInputMethodUndoState.active = true; 1462 break; 1463 case B_INPUT_METHOD_LOCATION_REQUEST: 1464 fInputMethodUndoState.active = true; 1465 break; 1466 } 1467 } 1468 BTextView::MessageReceived(msg); 1469 break; 1470 } 1471 1472 case M_REDO: 1473 Redo(); 1474 break; 1475 1476 default: 1477 BTextView::MessageReceived(msg); 1478 } 1479 } 1480 1481 1482 void 1483 TTextView::MouseDown(BPoint where) 1484 { 1485 if (IsEditable()) { 1486 BPoint point; 1487 uint32 buttons; 1488 GetMouse(&point, &buttons); 1489 if (gDictCount && (buttons == B_SECONDARY_MOUSE_BUTTON)) { 1490 int32 offset, start, end, length; 1491 const char *text = Text(); 1492 offset = OffsetAt(where); 1493 if (isalpha(text[offset])) { 1494 length = TextLength(); 1495 1496 //Find start and end of word 1497 //FindSpellBoundry(length, offset, &start, &end); 1498 1499 char c; 1500 bool isAlpha, isApost, isCap; 1501 int32 first; 1502 1503 for (first = offset; 1504 (first >= 0) && (((c = text[first]) == '\'') || isalpha(c)); 1505 first--) {} 1506 isCap = isupper(text[++first]); 1507 1508 for (start = offset, c = text[start], isAlpha = isalpha(c), isApost = (c=='\''); 1509 (start >= 0) && (isAlpha || (isApost 1510 && (((c = text[start+1]) != 's') || !isCap) && isalpha(c) 1511 && isalpha(text[start-1]))); 1512 start--, c = text[start], isAlpha = isalpha(c), isApost = (c == '\'')) {} 1513 start++; 1514 1515 for (end = offset, c = text[end], isAlpha = isalpha(c), isApost = (c == '\''); 1516 (end < length) && (isAlpha || (isApost 1517 && (((c = text[end + 1]) != 's') || !isCap) && isalpha(c))); 1518 end++, c = text[end], isAlpha = isalpha(c), isApost = (c == '\'')) {} 1519 1520 length = end - start; 1521 BString srcWord; 1522 srcWord.SetTo(text + start, length); 1523 1524 bool foundWord = false; 1525 BList matches; 1526 BString *string; 1527 1528 BMenuItem *menuItem; 1529 BPopUpMenu menu("Words", false, false); 1530 1531 int32 matchCount; 1532 for (int32 i = 0; i < gDictCount; i++) 1533 matchCount = gWords[i]->FindBestMatches(&matches, 1534 srcWord.String()); 1535 1536 if (matches.CountItems()) { 1537 sort_word_list(&matches, srcWord.String()); 1538 for (int32 i = 0; (string = (BString *)matches.ItemAt(i)) != NULL; i++) { 1539 menu.AddItem((menuItem = new BMenuItem(string->String(), NULL))); 1540 if (!strcasecmp(string->String(), srcWord.String())) { 1541 menuItem->SetEnabled(false); 1542 foundWord = true; 1543 } 1544 delete string; 1545 } 1546 } else { 1547 (menuItem = new BMenuItem("No Matches", NULL))->SetEnabled(false); 1548 menu.AddItem(menuItem); 1549 } 1550 1551 BMenuItem *addItem = NULL; 1552 if (!foundWord && gUserDict >= 0) { 1553 menu.AddSeparatorItem(); 1554 addItem = new BMenuItem(MDR_DIALECT_CHOICE ("Add", "追加"), NULL); 1555 menu.AddItem(addItem); 1556 } 1557 1558 point = ConvertToScreen(where); 1559 if ((menuItem = menu.Go(point, false, false)) != NULL) { 1560 if (menuItem == addItem) { 1561 BString newItem(srcWord.String()); 1562 newItem << "\n"; 1563 gWords[gUserDict]->InitIndex(); 1564 gExactWords[gUserDict]->InitIndex(); 1565 gUserDictFile->Write(newItem.String(), newItem.Length()); 1566 gWords[gUserDict]->BuildIndex(); 1567 gExactWords[gUserDict]->BuildIndex(); 1568 1569 if (fSpellCheck) 1570 CheckSpelling(0, TextLength()); 1571 } else { 1572 int32 len = strlen(menuItem->Label()); 1573 Select(start, start); 1574 Delete(start, end); 1575 Insert(start, menuItem->Label(), len); 1576 Select(start+len, start+len); 1577 } 1578 } 1579 } 1580 return; 1581 } else if (fSpellCheck && IsEditable()) { 1582 int32 start, end; 1583 1584 GetSelection(&start, &end); 1585 FindSpellBoundry(1, start, &start, &end); 1586 CheckSpelling(start, end); 1587 } 1588 } else { 1589 // is not editable, look for enclosures/links 1590 1591 int32 clickOffset = OffsetAt(where); 1592 int32 items = fEnclosures->CountItems(); 1593 for (int32 loop = 0; loop < items; loop++) { 1594 hyper_text *enclosure = (hyper_text*) fEnclosures->ItemAt(loop); 1595 if (clickOffset < enclosure->text_start || clickOffset >= enclosure->text_end) 1596 continue; 1597 1598 // 1599 // The user is clicking on this attachment 1600 // 1601 1602 int32 start; 1603 int32 finish; 1604 Select(enclosure->text_start, enclosure->text_end); 1605 GetSelection(&start, &finish); 1606 Window()->UpdateIfNeeded(); 1607 1608 bool drag = false; 1609 bool held = false; 1610 uint32 buttons = 0; 1611 if (Window()->CurrentMessage()) { 1612 Window()->CurrentMessage()->FindInt32("buttons", 1613 (int32 *) &buttons); 1614 } 1615 1616 // 1617 // If this is the primary button, wait to see if the user is going 1618 // to single click, hold, or drag. 1619 // 1620 if (buttons != B_SECONDARY_MOUSE_BUTTON) { 1621 BPoint point = where; 1622 bigtime_t popupDelay; 1623 get_click_speed(&popupDelay); 1624 popupDelay *= 2; 1625 popupDelay += system_time(); 1626 while (buttons && abs((int)(point.x - where.x)) < 4 1627 && abs((int)(point.y - where.y)) < 4 1628 && system_time() < popupDelay) { 1629 snooze(10000); 1630 GetMouse(&point, &buttons); 1631 } 1632 1633 if (system_time() < popupDelay) { 1634 // 1635 // The user either dragged this or released the button. 1636 // check if it was dragged. 1637 // 1638 if (!(abs((int)(point.x - where.x)) < 4 1639 && abs((int)(point.y - where.y)) < 4) && buttons) 1640 drag = true; 1641 } else { 1642 // 1643 // The user held the button down. 1644 // 1645 held = true; 1646 } 1647 } 1648 1649 // 1650 // If the user has right clicked on this menu, 1651 // or held the button down on it for a while, 1652 // pop up a context menu. 1653 // 1654 if (buttons == B_SECONDARY_MOUSE_BUTTON || held) { 1655 // 1656 // Right mouse click... Display a menu 1657 // 1658 BPoint point = where; 1659 ConvertToScreen(&point); 1660 1661 BMenuItem *item; 1662 if ((enclosure->type != TYPE_ENCLOSURE) 1663 && (enclosure->type != TYPE_BE_ENCLOSURE)) 1664 item = fLinkMenu->Go(point, true); 1665 else 1666 item = fEnclosureMenu->Go(point, true); 1667 1668 BMessage *msg; 1669 if (item && (msg = item->Message()) != NULL) { 1670 if (msg->what == M_SAVE) { 1671 if (fPanel) 1672 fPanel->SetEnclosure(enclosure); 1673 else { 1674 fPanel = new TSavePanel(enclosure, this); 1675 fPanel->Window()->Show(); 1676 } 1677 } else if (msg->what == M_COPY) { 1678 // copy link location to clipboard 1679 1680 if (be_clipboard->Lock()) { 1681 be_clipboard->Clear(); 1682 1683 BMessage *clip; 1684 if ((clip = be_clipboard->Data()) != NULL) { 1685 clip->AddData("text/plain", B_MIME_TYPE, 1686 enclosure->name, strlen(enclosure->name)); 1687 be_clipboard->Commit(); 1688 } 1689 be_clipboard->Unlock(); 1690 } 1691 } else 1692 Open(enclosure); 1693 } 1694 } else { 1695 // 1696 // Left button. If the user single clicks, open this link. 1697 // Otherwise, initiate a drag. 1698 // 1699 if (drag) { 1700 BMessage dragMessage(B_SIMPLE_DATA); 1701 dragMessage.AddInt32("be:actions", B_COPY_TARGET); 1702 dragMessage.AddString("be:types", B_FILE_MIME_TYPE); 1703 switch (enclosure->type) { 1704 case TYPE_BE_ENCLOSURE: 1705 case TYPE_ENCLOSURE: 1706 // 1707 // Attachment. The type is specified in the message. 1708 // 1709 dragMessage.AddString("be:types", B_FILE_MIME_TYPE); 1710 dragMessage.AddString("be:filetypes", 1711 enclosure->content_type ? enclosure->content_type : ""); 1712 dragMessage.AddString("be:clip_name", enclosure->name); 1713 break; 1714 1715 case TYPE_URL: 1716 // 1717 // URL. The user can drag it into the tracker to 1718 // create a bookmark file. 1719 // 1720 dragMessage.AddString("be:types", B_FILE_MIME_TYPE); 1721 dragMessage.AddString("be:filetypes", 1722 "application/x-vnd.Be-bookmark"); 1723 dragMessage.AddString("be:filetypes", "text/plain"); 1724 dragMessage.AddString("be:clip_name", "Bookmark"); 1725 1726 dragMessage.AddString("be:url", enclosure->name); 1727 break; 1728 1729 case TYPE_MAILTO: 1730 // 1731 // Mailto address. The user can drag it into the 1732 // tracker to create a people file. 1733 // 1734 dragMessage.AddString("be:types", B_FILE_MIME_TYPE); 1735 dragMessage.AddString("be:filetypes", 1736 "application/x-person"); 1737 dragMessage.AddString("be:filetypes", "text/plain"); 1738 dragMessage.AddString("be:clip_name", "Person"); 1739 1740 dragMessage.AddString("be:email", enclosure->name); 1741 break; 1742 1743 default: 1744 // 1745 // Otherwise it doesn't have a type that I know how 1746 // to save. It won't have any types and if any 1747 // program wants to accept it, more power to them. 1748 // (tracker won't.) 1749 // 1750 dragMessage.AddString("be:clip_name", "Hyperlink"); 1751 } 1752 1753 BMessage data; 1754 data.AddPointer("enclosure", enclosure); 1755 dragMessage.AddMessage("be:originator-data", &data); 1756 1757 BRegion selectRegion; 1758 GetTextRegion(start, finish, &selectRegion); 1759 DragMessage(&dragMessage, selectRegion.Frame(), this); 1760 } else { 1761 // 1762 // User Single clicked on the attachment. Open it. 1763 // 1764 Open(enclosure); 1765 } 1766 } 1767 return; 1768 } 1769 } 1770 BTextView::MouseDown(where); 1771 } 1772 1773 1774 void 1775 TTextView::MouseMoved(BPoint where, uint32 code, const BMessage *msg) 1776 { 1777 int32 start = OffsetAt(where); 1778 1779 for (int32 loop = fEnclosures->CountItems(); loop-- > 0;) { 1780 hyper_text *enclosure = (hyper_text *)fEnclosures->ItemAt(loop); 1781 if ((start >= enclosure->text_start) && (start < enclosure->text_end)) { 1782 if (!fCursor) 1783 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 1784 fCursor = true; 1785 return; 1786 } 1787 } 1788 1789 if (fCursor) { 1790 SetViewCursor(B_CURSOR_I_BEAM); 1791 fCursor = false; 1792 } 1793 1794 BTextView::MouseMoved(where, code, msg); 1795 } 1796 1797 1798 void 1799 TTextView::ClearList() 1800 { 1801 hyper_text *enclosure; 1802 while ((enclosure = (hyper_text *)fEnclosures->FirstItem()) != NULL) { 1803 fEnclosures->RemoveItem(enclosure); 1804 1805 if (enclosure->name) 1806 free(enclosure->name); 1807 if (enclosure->content_type) 1808 free(enclosure->content_type); 1809 if (enclosure->encoding) 1810 free(enclosure->encoding); 1811 if (enclosure->have_ref && !enclosure->saved) { 1812 BEntry entry(&enclosure->ref); 1813 entry.Remove(); 1814 } 1815 1816 watch_node(&enclosure->node, B_STOP_WATCHING, this); 1817 free(enclosure); 1818 } 1819 } 1820 1821 1822 void 1823 TTextView::LoadMessage(BEmailMessage *mail, bool quoteIt, const char *text) 1824 { 1825 StopLoad(); 1826 1827 fMail = mail; 1828 1829 ClearList(); 1830 1831 MakeSelectable(true); 1832 MakeEditable(false); 1833 if (text) 1834 Insert(text, strlen(text)); 1835 1836 //attr_info attrInfo; 1837 TTextView::Reader *reader = new TTextView::Reader(fHeader, fRaw, quoteIt, fIncoming, 1838 text != NULL, true, 1839 // I removed the following, because I absolutely can't imagine why it's 1840 // there (the mail kit should be able to deal with non-compliant mails) 1841 // -- axeld. 1842 // fFile->GetAttrInfo(B_MAIL_ATTR_MIME, &attrInfo) == B_OK, 1843 this, mail, fEnclosures, fStopSem); 1844 1845 resume_thread(fThread = spawn_thread(Reader::Run, "reader", B_NORMAL_PRIORITY, reader)); 1846 } 1847 1848 1849 void 1850 TTextView::Open(hyper_text *enclosure) 1851 { 1852 switch (enclosure->type) { 1853 case TYPE_URL: 1854 { 1855 const struct {const char *urlType, *handler; } handlerTable[] = { 1856 {"http", B_URL_HTTP}, 1857 {"https", B_URL_HTTPS}, 1858 {"ftp", B_URL_FTP}, 1859 {"gopher", B_URL_GOPHER}, 1860 {"mailto", B_URL_MAILTO}, 1861 {"news", B_URL_NEWS}, 1862 {"nntp", B_URL_NNTP}, 1863 {"telnet", B_URL_TELNET}, 1864 {"rlogin", B_URL_RLOGIN}, 1865 {"tn3270", B_URL_TN3270}, 1866 {"wais", B_URL_WAIS}, 1867 {"file", B_URL_FILE}, 1868 {NULL, NULL} 1869 }; 1870 const char *handlerToLaunch = NULL; 1871 1872 const char *colonPos = strchr(enclosure->name, ':'); 1873 if (colonPos) { 1874 int urlTypeLength = colonPos - enclosure->name; 1875 1876 for (int32 index = 0; handlerTable[index].urlType; index++) { 1877 if (!strncasecmp(enclosure->name, 1878 handlerTable[index].urlType, urlTypeLength)) { 1879 handlerToLaunch = handlerTable[index].handler; 1880 break; 1881 } 1882 } 1883 } 1884 if (handlerToLaunch) { 1885 entry_ref appRef; 1886 if (be_roster->FindApp(handlerToLaunch, &appRef) != B_OK) 1887 handlerToLaunch = NULL; 1888 } 1889 if (!handlerToLaunch) 1890 handlerToLaunch = "application/x-vnd.Be-Bookmark"; 1891 1892 status_t result = be_roster->Launch(handlerToLaunch, 1, &enclosure->name); 1893 if (result != B_NO_ERROR && result != B_ALREADY_RUNNING) { 1894 beep(); 1895 (new BAlert("", 1896 MDR_DIALECT_CHOICE("There is no installed handler for URL links.", 1897 "このURLリンクを扱えるアプリケーションが存在しません"), 1898 "Sorry"))->Go(); 1899 } 1900 break; 1901 } 1902 1903 case TYPE_MAILTO: 1904 if (be_roster->Launch(B_MAIL_TYPE, 1, &enclosure->name) < B_OK) { 1905 char *argv[] = {"Mail", enclosure->name}; 1906 be_app->ArgvReceived(2, argv); 1907 } 1908 break; 1909 1910 case TYPE_ENCLOSURE: 1911 case TYPE_BE_ENCLOSURE: 1912 if (!enclosure->have_ref) { 1913 BPath path; 1914 if (find_directory(B_COMMON_TEMP_DIRECTORY, &path) == B_NO_ERROR) { 1915 BDirectory dir(path.Path()); 1916 if (dir.InitCheck() == B_NO_ERROR) { 1917 char name[B_FILE_NAME_LENGTH]; 1918 char baseName[B_FILE_NAME_LENGTH]; 1919 strcpy(baseName, enclosure->name ? enclosure->name : "enclosure"); 1920 strcpy(name, baseName); 1921 for (int32 index = 0; dir.Contains(name); index++) 1922 sprintf(name, "%s_%ld", baseName, index); 1923 1924 BEntry entry(path.Path()); 1925 entry_ref ref; 1926 entry.GetRef(&ref); 1927 1928 BMessage save(M_SAVE); 1929 save.AddRef("directory", &ref); 1930 save.AddString("name", name); 1931 save.AddPointer("enclosure", enclosure); 1932 if (Save(&save) != B_NO_ERROR) 1933 break; 1934 enclosure->saved = false; 1935 } 1936 } 1937 } 1938 1939 BMessenger tracker("application/x-vnd.Be-TRAK"); 1940 if (tracker.IsValid()) { 1941 BMessage openMsg(B_REFS_RECEIVED); 1942 openMsg.AddRef("refs", &enclosure->ref); 1943 tracker.SendMessage(&openMsg); 1944 } 1945 break; 1946 } 1947 } 1948 1949 1950 status_t 1951 TTextView::Save(BMessage *msg, bool makeNewFile) 1952 { 1953 const char *name; 1954 entry_ref ref; 1955 BFile file; 1956 BPath path; 1957 hyper_text *enclosure; 1958 status_t result = B_NO_ERROR; 1959 char entry_name[B_FILE_NAME_LENGTH]; 1960 1961 msg->FindString("name", &name); 1962 msg->FindRef("directory", &ref); 1963 msg->FindPointer("enclosure", (void **)&enclosure); 1964 1965 BDirectory dir; 1966 dir.SetTo(&ref); 1967 result = dir.InitCheck(); 1968 1969 if (result == B_OK) { 1970 if (makeNewFile) { 1971 // 1972 // Search for the file and delete it if it already exists. 1973 // (It may not, that's ok.) 1974 // 1975 BEntry entry; 1976 if (dir.FindEntry(name, &entry) == B_NO_ERROR) 1977 entry.Remove(); 1978 1979 if ((enclosure->have_ref) && (!enclosure->saved)) { 1980 entry.SetTo(&enclosure->ref); 1981 1982 // 1983 // Added true arg and entry_name so MoveTo clobbers as 1984 // before. This may not be the correct behaviour, but 1985 // it's the preserved behaviour. 1986 // 1987 entry.GetName(entry_name); 1988 result = entry.MoveTo(&dir, entry_name, true); 1989 if (result == B_NO_ERROR) { 1990 entry.Rename(name); 1991 entry.GetRef(&enclosure->ref); 1992 entry.GetNodeRef(&enclosure->node); 1993 enclosure->saved = true; 1994 return result; 1995 } 1996 } 1997 1998 if (result == B_NO_ERROR) { 1999 result = dir.CreateFile(name, &file); 2000 if (result == B_NO_ERROR && enclosure->content_type) { 2001 char type[B_MIME_TYPE_LENGTH]; 2002 2003 if (!strcasecmp(enclosure->content_type, "message/rfc822")) 2004 strcpy(type, "text/x-email"); 2005 else if (!strcasecmp(enclosure->content_type, "message/delivery-status")) 2006 strcpy(type, "text/plain"); 2007 else 2008 strcpy(type, enclosure->content_type); 2009 2010 BNodeInfo info(&file); 2011 info.SetType(type); 2012 } 2013 } 2014 } else { 2015 // 2016 // This file was dragged into the tracker or desktop. The file 2017 // already exists. 2018 // 2019 result = file.SetTo(&dir, name, B_WRITE_ONLY); 2020 } 2021 } 2022 2023 if (enclosure->component == NULL) 2024 result = B_ERROR; 2025 2026 if (result == B_NO_ERROR) { 2027 // 2028 // Write the data 2029 // 2030 enclosure->component->GetDecodedData(&file); 2031 2032 BEntry entry; 2033 dir.FindEntry(name, &entry); 2034 entry.GetRef(&enclosure->ref); 2035 enclosure->have_ref = true; 2036 enclosure->saved = true; 2037 entry.GetPath(&path); 2038 update_mime_info(path.Path(), false, true, 2039 !cistrcmp("application/octet-stream", enclosure->content_type ? enclosure->content_type : B_EMPTY_STRING)); 2040 entry.GetNodeRef(&enclosure->node); 2041 watch_node(&enclosure->node, B_WATCH_NAME, this); 2042 } 2043 2044 if (result != B_NO_ERROR) { 2045 beep(); 2046 MDR_DIALECT_CHOICE( 2047 (new BAlert("", "An error occurred trying to save the enclosure.", "Sorry"))->Go();, 2048 (new BAlert("", "添付ファイルを保存するときにエラーが発生しました", "了解"))->Go(); 2049 ) 2050 } 2051 2052 return result; 2053 } 2054 2055 2056 void 2057 TTextView::StopLoad() 2058 { 2059 Window()->Unlock(); 2060 2061 thread_info info; 2062 if (fThread != 0 && get_thread_info(fThread, &info) == B_NO_ERROR) { 2063 fStopLoading = true; 2064 acquire_sem(fStopSem); 2065 int32 result; 2066 wait_for_thread(fThread, &result); 2067 fThread = 0; 2068 release_sem(fStopSem); 2069 fStopLoading = false; 2070 } 2071 2072 Window()->Lock(); 2073 } 2074 2075 2076 void 2077 TTextView::AddAsContent(BEmailMessage *mail, bool wrap, uint32 charset, mail_encoding encoding) 2078 { 2079 if (mail == NULL) 2080 return; 2081 2082 int32 textLength = TextLength(); 2083 const char *text = Text(); 2084 2085 BTextMailComponent *body = mail->Body(); 2086 if (body == NULL) { 2087 if (mail->SetBody(body = new BTextMailComponent()) < B_OK) 2088 return; 2089 } 2090 body->SetEncoding(encoding, charset); 2091 2092 // Just add the text as a whole if we can, or ... 2093 if (!wrap) { 2094 body->AppendText(text); 2095 return; 2096 } 2097 2098 // ... do word wrapping. 2099 2100 BWindow *window = Window(); 2101 char *saveText = strdup(text); 2102 BRect saveTextRect = TextRect(); 2103 2104 // do this before we start messing with the fonts 2105 // the user will never know... 2106 window->DisableUpdates(); 2107 Hide(); 2108 BScrollBar *vScroller = ScrollBar(B_VERTICAL); 2109 BScrollBar *hScroller = ScrollBar(B_HORIZONTAL); 2110 if (vScroller != NULL) 2111 vScroller->SetTarget((BView *)NULL); 2112 if (hScroller != NULL) 2113 hScroller->SetTarget((BView *)NULL); 2114 2115 // Temporarily set the font to a fixed width font for line wrapping 2116 // calculations. If the font doesn't have as many of the symbols as 2117 // the preferred font, go back to using the user's preferred font. 2118 2119 bool *boolArray; 2120 int missingCharactersFixedWidth = 0; 2121 int missingCharactersPreferredFont = 0; 2122 int32 numberOfCharacters; 2123 2124 numberOfCharacters = BString(text).CountChars(); 2125 if (numberOfCharacters > 0 2126 && (boolArray = (bool *)malloc(sizeof(bool) * numberOfCharacters)) != NULL) { 2127 memset(boolArray, 0, sizeof (bool) * numberOfCharacters); 2128 be_fixed_font->GetHasGlyphs(text, numberOfCharacters, boolArray); 2129 for (int i = 0; i < numberOfCharacters; i++) { 2130 if (!boolArray[i]) 2131 missingCharactersFixedWidth += 1; 2132 } 2133 2134 memset(boolArray, 0, sizeof (bool) * numberOfCharacters); 2135 fFont.GetHasGlyphs(text, numberOfCharacters, boolArray); 2136 for (int i = 0; i < numberOfCharacters; i++) { 2137 if (!boolArray[i]) 2138 missingCharactersPreferredFont += 1; 2139 } 2140 2141 free(boolArray); 2142 } 2143 2144 if (missingCharactersFixedWidth > missingCharactersPreferredFont) 2145 SetFontAndColor(0, textLength, &fFont); 2146 else // All things being equal, the fixed font is better for wrapping. 2147 SetFontAndColor(0, textLength, be_fixed_font); 2148 2149 // calculate a text rect that is 72 columns wide 2150 BRect newTextRect = saveTextRect; 2151 newTextRect.right = newTextRect.left + be_fixed_font->StringWidth("m") * 72; 2152 SetTextRect(newTextRect); 2153 2154 // hard-wrap, based on TextView's soft-wrapping 2155 int32 numLines = CountLines(); 2156 bool spaceMoved = false; 2157 char *content = (char *)malloc(textLength + numLines * 72); // more we'll ever need 2158 if (content != NULL) { 2159 int32 contentLength = 0; 2160 2161 for (int32 i = 0; i < numLines; i++) { 2162 int32 startOffset = OffsetAt(i); 2163 if (spaceMoved) { 2164 startOffset++; 2165 spaceMoved = false; 2166 } 2167 int32 endOffset = OffsetAt(i + 1); 2168 int32 lineLength = endOffset - startOffset; 2169 2170 // quick hack to not break URLs into several parts 2171 for (int32 pos = startOffset; pos < endOffset; pos++) { 2172 size_t urlLength; 2173 uint8 type = CheckForURL(text + pos, urlLength); 2174 if (type != 0) 2175 pos += urlLength; 2176 2177 if (pos > endOffset) { 2178 // find first break character after the URL 2179 for (; text[pos]; pos++) { 2180 if (isalnum(text[pos]) || isspace(text[pos])) 2181 break; 2182 } 2183 if (text[pos] && isspace(text[pos]) && text[pos] != '\n') 2184 pos++; 2185 2186 endOffset += pos - endOffset; 2187 lineLength = endOffset - startOffset; 2188 2189 // insert a newline (and the same number of quotes) after the 2190 // URL to make sure the rest of the text is properly wrapped 2191 2192 char buffer[64]; 2193 if (text[pos] == '\n') 2194 buffer[0] = '\0'; 2195 else 2196 strcpy(buffer, "\n"); 2197 2198 size_t quoteLength; 2199 CopyQuotes(text + startOffset, lineLength, buffer + strlen(buffer), quoteLength); 2200 2201 Insert(pos, buffer, strlen(buffer)); 2202 numLines = CountLines(); 2203 text = Text(); 2204 i++; 2205 } 2206 } 2207 if (text[endOffset - 1] != ' ' 2208 && text[endOffset - 1] != '\n' 2209 && text[endOffset] == ' ') { 2210 // make sure spaces will be part of this line 2211 endOffset++; 2212 lineLength++; 2213 spaceMoved = true; 2214 } 2215 2216 memcpy(content + contentLength, text + startOffset, lineLength); 2217 contentLength += lineLength; 2218 2219 // add a newline to every line except for the ones 2220 // that already end in newlines, and the last line 2221 if ((text[endOffset - 1] != '\n') && (i < (numLines - 1))) { 2222 content[contentLength++] = '\n'; 2223 2224 // copy quote level of the first line 2225 size_t quoteLength; 2226 CopyQuotes(text + startOffset, lineLength, content + contentLength, quoteLength); 2227 contentLength += quoteLength; 2228 } 2229 } 2230 content[contentLength] = '\0'; 2231 2232 body->AppendText(content); 2233 free(content); 2234 } 2235 2236 // reset the text rect and font 2237 SetTextRect(saveTextRect); 2238 SetText(saveText); 2239 free(saveText); 2240 SetFontAndColor(0, textLength, &fFont); 2241 2242 // should be OK to hook these back up now 2243 if (vScroller != NULL) 2244 vScroller->SetTarget(this); 2245 if (hScroller != NULL) 2246 hScroller->SetTarget(this); 2247 2248 Show(); 2249 window->EnableUpdates(); 2250 } 2251 2252 2253 // #pragma mark - 2254 2255 2256 TTextView::Reader::Reader(bool header, bool raw, bool quote, bool incoming, 2257 bool stripHeader, bool mime, TTextView *view, BEmailMessage *mail, 2258 BList *list, sem_id sem) 2259 : 2260 fHeader(header), 2261 fRaw(raw), 2262 fQuote(quote), 2263 fIncoming(incoming), 2264 fStripHeader(stripHeader), 2265 fMime(mime), 2266 fView(view), 2267 fMail(mail), 2268 fEnclosures(list), 2269 fStopSem(sem) 2270 { 2271 } 2272 2273 2274 bool 2275 TTextView::Reader::ParseMail(BMailContainer *container, 2276 BTextMailComponent *ignore) 2277 { 2278 int32 count = 0; 2279 for (int32 i = 0; i < container->CountComponents(); i++) { 2280 if (fView->fStopLoading) 2281 return false; 2282 2283 BMailComponent *component; 2284 if ((component = container->GetComponent(i)) == NULL) { 2285 if (fView->fStopLoading) 2286 return false; 2287 2288 hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text)); 2289 memset(enclosure, 0, sizeof(hyper_text)); 2290 2291 enclosure->type = TYPE_ENCLOSURE; 2292 2293 const char *name = "\n<Enclosure: could not handle>\n"; 2294 2295 fView->GetSelection(&enclosure->text_start, &enclosure->text_end); 2296 enclosure->text_start++; 2297 enclosure->text_end += strlen(name) - 1; 2298 2299 Insert(name, strlen(name), true); 2300 fEnclosures->AddItem(enclosure); 2301 continue; 2302 } 2303 2304 count++; 2305 if (component == ignore) 2306 continue; 2307 2308 if (component->ComponentType() == B_MAIL_MULTIPART_CONTAINER) { 2309 BMIMEMultipartMailContainer *c = dynamic_cast<BMIMEMultipartMailContainer *>(container->GetComponent(i)); 2310 ASSERT(c != NULL); 2311 2312 if (!ParseMail(c, ignore)) 2313 count--; 2314 } else if (fIncoming) { 2315 hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text)); 2316 memset(enclosure, 0, sizeof(hyper_text)); 2317 2318 enclosure->type = TYPE_ENCLOSURE; 2319 enclosure->component = component; 2320 2321 BString name; 2322 char fileName[B_FILE_NAME_LENGTH]; 2323 strcpy(fileName, "untitled"); 2324 if (BMailAttachment *attachment = dynamic_cast <BMailAttachment *> (component)) 2325 attachment->FileName(fileName); 2326 2327 BPath path(fileName); 2328 enclosure->name = strdup(path.Leaf()); 2329 2330 BMimeType type; 2331 component->MIMEType(&type); 2332 enclosure->content_type = strdup(type.Type()); 2333 2334 char typeDescription[B_MIME_TYPE_LENGTH]; 2335 if (type.GetShortDescription(typeDescription) != B_OK) 2336 strcpy(typeDescription, type.Type() ? type.Type() : B_EMPTY_STRING); 2337 2338 name = "\n<Enclosure: "; 2339 name << enclosure->name << " (Type: " << typeDescription << ")>\n"; 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 MDR_DIALECT_CHOICE("Inconsistency occurred in the Undo/Redo buffer.", 3289 "Undo/Redoバッファに矛盾が発生しました!"), "OK"))->Go(); 3290 } 3291 break; 3292 } 3293 ScrollToSelection(); 3294 ContentChanged(); 3295 fUndoBuffer.On(); 3296 } 3297 } 3298 3299 3300 void 3301 TTextView::Redo() 3302 { 3303 if (fInputMethodUndoState.active) 3304 return; 3305 3306 int32 length, offset, cursorPos; 3307 undo_type history; 3308 char *text; 3309 status_t status; 3310 bool replaced; 3311 3312 status = fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced); 3313 if (status == B_OK) { 3314 fUndoBuffer.Off(); 3315 3316 switch (history) { 3317 case K_INSERTED: 3318 BTextView::Insert(offset, text, length); 3319 Select(offset, offset + length); 3320 break; 3321 3322 case K_DELETED: 3323 BTextView::Delete(offset, offset + length); 3324 if (replaced) { 3325 fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced); 3326 BTextView::Insert(offset, text, length); 3327 } 3328 Select(offset, offset + length); 3329 break; 3330 3331 case K_REPLACED: 3332 ::beep(); 3333 (new BAlert("", 3334 MDR_DIALECT_CHOICE("Inconsistency occurred in the Undo/Redo buffer.", 3335 "Undo/Redoバッファに矛盾が発生しました!"), "OK"))->Go(); 3336 break; 3337 } 3338 ScrollToSelection(); 3339 ContentChanged(); 3340 fUndoBuffer.On(); 3341 } 3342 } 3343