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