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