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 #include "Mail.h" 36 #include "Header.h" 37 #include "Utilities.h" 38 #include "QueryMenu.h" 39 #include "FieldMsg.h" 40 #include "Prefs.h" 41 42 #include <MDRLanguage.h> 43 #include <MailSettings.h> 44 #include <MailMessage.h> 45 46 #include <CharacterSet.h> 47 #include <CharacterSetRoster.h> 48 #include <E-mail.h> 49 #include <MenuField.h> 50 #include <MenuItem.h> 51 #include <PopUpMenu.h> 52 #include <Query.h> 53 #include <String.h> 54 #include <StringView.h> 55 #include <Volume.h> 56 #include <VolumeRoster.h> 57 #include <Window.h> 58 #include <fs_index.h> 59 #include <fs_info.h> 60 61 #include <ctype.h> 62 #include <map> 63 #include <stdio.h> 64 #include <stdlib.h> 65 #include <string.h> 66 #include <time.h> 67 68 69 using namespace BPrivate; 70 using std::map; 71 72 extern uint32 gDefaultChain; 73 74 const char* kDateLabel = "Date:"; 75 const uint32 kMsgFrom = 'hFrm'; 76 const uint32 kMsgEncoding = 'encd'; 77 const uint32 kMsgAddressChosen = 'acsn'; 78 79 80 class QPopupMenu : public QueryMenu { 81 public: 82 QPopupMenu(const char *title); 83 84 private: 85 void AddPersonItem(const entry_ref *ref, ino_t node, BString &name, 86 BString &email, const char *attr, BMenu *groupMenu, 87 BMenuItem *superItem); 88 89 protected: 90 virtual void EntryCreated(const entry_ref &ref, ino_t node); 91 virtual void EntryRemoved(ino_t node); 92 93 int32 fGroups; // Current number of "group" submenus. Includes All People if present. 94 }; 95 96 struct CompareBStrings { 97 bool 98 operator()(const BString *s1, const BString *s2) const 99 { 100 return (s1->Compare(*s2) < 0); 101 } 102 }; 103 104 105 const char* 106 mail_to_filter(const char* text, int32& length, const text_run_array*& runs) 107 { 108 if (!strncmp(text, "mailto:", 7)) { 109 text += 7; 110 length -= 7; 111 if (runs != NULL) 112 runs = NULL; 113 } 114 115 return text; 116 } 117 118 119 // #pragma mark - THeaderView 120 121 122 THeaderView::THeaderView(BRect rect, BRect windowRect, bool incoming, 123 BEmailMessage *mail, bool resending, uint32 defaultCharacterSet) 124 : BBox(rect, "m_header", B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW, B_NO_BORDER), 125 fAccountMenu(NULL), 126 fEncodingMenu(NULL), 127 fChain(gDefaultChain), 128 fAccountTo(NULL), 129 fAccount(NULL), 130 fBcc(NULL), 131 fCc(NULL), 132 fSubject(NULL), 133 fTo(NULL), 134 fDate(NULL), 135 fIncoming(incoming), 136 fCharacterSetUserSees(defaultCharacterSet), 137 fResending(resending), 138 fBccMenu(NULL), 139 fCcMenu(NULL), 140 fToMenu(NULL), 141 fEmailList(NULL) 142 { 143 BMenuField* field; 144 BMessage* msg; 145 146 BFont font = *be_plain_font; 147 font.SetSize(FONT_SIZE); 148 SetFont(&font); 149 float x = font.StringWidth( /* The longest title string in the header area */ 150 MDR_DIALECT_CHOICE ("Enclosures: ","添付ファイル:")) + 9; 151 float y = TO_FIELD_V; 152 153 if (!fIncoming) { 154 InitEmailCompletion(); 155 InitGroupCompletion(); 156 } 157 158 // Prepare the character set selection pop-up menu (we tell the user that 159 // it is the Encoding menu, even though it is really the character set). 160 // It may appear in the first line, to the right of the From box if the 161 // user is reading an e-mail. It appears on the second line, to the right 162 // of the e-mail account menu, if the user is composing a message. It lets 163 // the user quickly select a character set different from the application 164 // wide default one, and also shows them which character set is active. If 165 // you are reading a message, you also see an item that says "Automatic" 166 // for automatic decoding character set choice. It can slide around as the 167 // window is resized when viewing a message, but not when composing 168 // (because the adjacent pop-up menu can't resize dynamically due to a BeOS 169 // bug). 170 171 float widestCharacterSet = 0; 172 bool markedCharSet = false; 173 BMenuItem* item; 174 175 fEncodingMenu = new BPopUpMenu(B_EMPTY_STRING); 176 177 BCharacterSetRoster roster; 178 BCharacterSet charset; 179 while (roster.GetNextCharacterSet(&charset) == B_OK) { 180 BString name(charset.GetPrintName()); 181 const char* mime = charset.GetMIMEName(); 182 if (mime) 183 name << " (" << mime << ")"; 184 185 uint32 convertID; 186 if (mime == NULL || strcasecmp(mime, "UTF-8") != 0) 187 convertID = charset.GetConversionID(); 188 else 189 convertID = B_MAIL_UTF8_CONVERSION; 190 191 msg = new BMessage(kMsgEncoding); 192 msg->AddInt32("charset", convertID); 193 fEncodingMenu->AddItem(item = new BMenuItem(name.String(), msg)); 194 if (convertID == fCharacterSetUserSees && !markedCharSet) { 195 item->SetMarked(true); 196 markedCharSet = true; 197 } 198 if (font.StringWidth(name.String()) > widestCharacterSet) 199 widestCharacterSet = font.StringWidth(name.String()); 200 } 201 202 msg = new BMessage(kMsgEncoding); 203 msg->AddInt32("charset", B_MAIL_US_ASCII_CONVERSION); 204 fEncodingMenu->AddItem(item = new BMenuItem("US-ASCII", msg)); 205 if (fCharacterSetUserSees == B_MAIL_US_ASCII_CONVERSION && !markedCharSet) { 206 item->SetMarked(true); 207 markedCharSet = true; 208 } 209 210 if (!resending && fIncoming) { 211 // reading a message, display the Automatic item 212 fEncodingMenu->AddSeparatorItem(); 213 msg = new BMessage(kMsgEncoding); 214 msg->AddInt32("charset", B_MAIL_NULL_CONVERSION); 215 fEncodingMenu->AddItem(item = new BMenuItem("Automatic", msg)); 216 if (!markedCharSet) 217 item->SetMarked(true); 218 } 219 220 // First line of the header, From for reading e-mails (includes the 221 // character set choice at the right), To when composing (nothing else in 222 // the row). 223 224 BRect r; 225 char string[20]; 226 if (fIncoming && !resending) { 227 // Set up the character set pop-up menu on the right of "To" box. 228 r.Set (windowRect.Width() - widestCharacterSet - 229 font.StringWidth (DECODING_TEXT) - 2 * SEPARATOR_MARGIN, 230 y - 2, windowRect.Width() - SEPARATOR_MARGIN, y + TO_FIELD_HEIGHT + 2); 231 field = new BMenuField (r, "decoding", DECODING_TEXT, fEncodingMenu, 232 true /* fixedSize */, 233 B_FOLLOW_TOP | B_FOLLOW_RIGHT, 234 B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); 235 field->SetFont (&font); 236 field->SetDivider (font.StringWidth(DECODING_TEXT) + 5); 237 AddChild(field); 238 r.Set(x - font.StringWidth(FROM_TEXT) - 11, y, 239 field->Frame().left - SEPARATOR_MARGIN, y + TO_FIELD_HEIGHT); 240 sprintf(string, FROM_TEXT); 241 } else { 242 r.Set(x - 11, y, windowRect.Width() - SEPARATOR_MARGIN, y + TO_FIELD_HEIGHT); 243 string[0] = 0; 244 } 245 246 y += FIELD_HEIGHT; 247 fTo = new TTextControl(r, string, new BMessage(TO_FIELD), fIncoming, resending, 248 B_FOLLOW_LEFT_RIGHT); 249 fTo->SetFilter(mail_to_filter); 250 251 if (!fIncoming || resending) { 252 fTo->SetChoiceList(&fEmailList); 253 fTo->SetAutoComplete(true); 254 } 255 256 AddChild(fTo); 257 msg = new BMessage(FIELD_CHANGED); 258 msg->AddInt32("bitmask", FIELD_TO); 259 fTo->SetModificationMessage(msg); 260 261 if (!fIncoming || resending) { 262 r.right = r.left + 8; 263 r.left = r.right - be_plain_font->StringWidth(TO_TEXT) - 30; 264 r.top -= 1; 265 fToMenu = new QPopupMenu(TO_TEXT); 266 field = new BMenuField(r, "", "", fToMenu, B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW); 267 field->SetDivider(0.0); 268 field->SetEnabled(true); 269 AddChild(field); 270 } 271 272 // "From:" accounts Menu and Encoding Menu. 273 if (!fIncoming || resending) { 274 // Put the character set box on the right of the From field. 275 r.Set(windowRect.Width() - widestCharacterSet - 276 font.StringWidth(ENCODING_TEXT) - 2 * SEPARATOR_MARGIN, 277 y - 2, windowRect.Width() - SEPARATOR_MARGIN, y + TO_FIELD_HEIGHT + 2); 278 field = new BMenuField (r, "encoding", ENCODING_TEXT, fEncodingMenu, 279 true /* fixedSize */, 280 B_FOLLOW_TOP | B_FOLLOW_LEFT, 281 B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); 282 field->SetFont(&font); 283 field->SetDivider(font.StringWidth(ENCODING_TEXT) + 5); 284 AddChild(field); 285 286 // And now the "from account" pop-up menu, on the left side, taking the 287 // remaining space. 288 289 fAccountMenu = new BPopUpMenu(B_EMPTY_STRING); 290 291 BList chains; 292 if (GetOutboundMailChains(&chains) >= B_OK) { 293 bool marked = false; 294 for (int32 i = 0; i < chains.CountItems(); i++) { 295 BMailChain *chain = (BMailChain *)chains.ItemAt(i); 296 BString name = chain->Name(); 297 if ((msg = chain->MetaData()) != NULL) { 298 name << ": " << msg->FindString("real_name") 299 << " <" << msg->FindString("reply_to") << ">"; 300 } 301 BMenuItem *item = new BMenuItem(name.String(), 302 msg = new BMessage(kMsgFrom)); 303 304 msg->AddInt32("id", chain->ID()); 305 306 if (gDefaultChain == chain->ID()) { 307 item->SetMarked(true); 308 marked = true; 309 } 310 fAccountMenu->AddItem(item); 311 delete chain; 312 } 313 314 if (!marked) { 315 BMenuItem *item = fAccountMenu->ItemAt(0); 316 if (item != NULL) { 317 item->SetMarked(true); 318 fChain = item->Message()->FindInt32("id"); 319 } else { 320 fAccountMenu->AddItem(item = new BMenuItem("<none>",NULL)); 321 item->SetEnabled(false); 322 fChain = ~0UL; 323 } 324 // default chain is invalid, set to marked 325 gDefaultChain = fChain; 326 } 327 } 328 r.Set(x - font.StringWidth(FROM_TEXT) - 11, y - 2, 329 field->Frame().left - SEPARATOR_MARGIN, y + TO_FIELD_HEIGHT + 2); 330 field = new BMenuField(r, "account", FROM_TEXT, fAccountMenu, 331 true /* fixedSize */, 332 B_FOLLOW_TOP | B_FOLLOW_LEFT, 333 B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); 334 field->SetFont(&font); 335 field->SetDivider(font.StringWidth(FROM_TEXT) + 11); 336 AddChild(field); 337 338 y += FIELD_HEIGHT; 339 } else { 340 // To: account 341 bool account = count_pop_accounts() > 0; 342 343 r.Set(x - font.StringWidth(TO_TEXT) - 11, y, 344 windowRect.Width() - SEPARATOR_MARGIN, y + TO_FIELD_HEIGHT); 345 if (account) 346 r.right -= SEPARATOR_MARGIN + ACCOUNT_FIELD_WIDTH; 347 fAccountTo = new TTextControl(r, TO_TEXT, NULL, fIncoming, false, B_FOLLOW_LEFT_RIGHT); 348 fAccountTo->SetEnabled(false); 349 AddChild(fAccountTo); 350 351 if (account) { 352 r.left = r.right + 6; r.right = windowRect.Width() - SEPARATOR_MARGIN; 353 fAccount = new TTextControl(r, ACCOUNT_TEXT, NULL, fIncoming, false, B_FOLLOW_RIGHT | B_FOLLOW_TOP); 354 fAccount->SetEnabled(false); 355 AddChild(fAccount); 356 } 357 y += FIELD_HEIGHT; 358 } 359 360 --y; 361 r.Set(x - font.StringWidth(SUBJECT_TEXT) - 11, y, 362 windowRect.Width() - SEPARATOR_MARGIN, y + TO_FIELD_HEIGHT); 363 y += FIELD_HEIGHT; 364 fSubject = new TTextControl(r, SUBJECT_TEXT, new BMessage(SUBJECT_FIELD), 365 fIncoming, false, B_FOLLOW_LEFT_RIGHT); 366 AddChild(fSubject); 367 (msg = new BMessage(FIELD_CHANGED))->AddInt32("bitmask", FIELD_SUBJECT); 368 fSubject->SetModificationMessage(msg); 369 370 if (fResending) 371 fSubject->SetEnabled(false); 372 373 --y; 374 375 if (!fIncoming) { 376 r.Set(x - 11, y, CC_FIELD_H + CC_FIELD_WIDTH, y + CC_FIELD_HEIGHT); 377 fCc = new TTextControl(r, "", new BMessage(CC_FIELD), fIncoming, false); 378 fCc->SetFilter(mail_to_filter); 379 fCc->SetChoiceList(&fEmailList); 380 fCc->SetAutoComplete(true); 381 AddChild(fCc); 382 (msg = new BMessage(FIELD_CHANGED))->AddInt32("bitmask", FIELD_CC); 383 fCc->SetModificationMessage(msg); 384 385 r.right = r.left + 9; 386 r.left = r.right - be_plain_font->StringWidth(CC_TEXT) - 30; 387 r.top -= 1; 388 fCcMenu = new QPopupMenu(CC_TEXT); 389 field = new BMenuField(r, "", "", fCcMenu, B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW); 390 391 field->SetDivider(0.0); 392 field->SetEnabled(true); 393 AddChild(field); 394 395 r.Set(BCC_FIELD_H + be_plain_font->StringWidth(BCC_TEXT), y, 396 windowRect.Width() - SEPARATOR_MARGIN, y + BCC_FIELD_HEIGHT); 397 y += FIELD_HEIGHT; 398 fBcc = new TTextControl(r, "", new BMessage(BCC_FIELD), 399 fIncoming, false, B_FOLLOW_LEFT_RIGHT); 400 fBcc->SetFilter(mail_to_filter); 401 fBcc->SetChoiceList(&fEmailList); 402 fBcc->SetAutoComplete(true); 403 AddChild(fBcc); 404 (msg = new BMessage(FIELD_CHANGED))->AddInt32("bitmask", FIELD_BCC); 405 fBcc->SetModificationMessage(msg); 406 407 r.right = r.left + 9; 408 r.left = r.right - be_plain_font->StringWidth(BCC_TEXT) - 30; 409 r.top -= 1; 410 fBccMenu = new QPopupMenu(BCC_TEXT); 411 field = new BMenuField(r, "", "", fBccMenu, B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW); 412 field->SetDivider(0.0); 413 field->SetEnabled(true); 414 AddChild(field); 415 } else { 416 r.Set(x - font.StringWidth(kDateLabel) - 10, y + 4, 417 windowRect.Width(), y + TO_FIELD_HEIGHT + 1); 418 y += TO_FIELD_HEIGHT + 5; 419 fDate = new BStringView(r, "", ""); 420 AddChild(fDate); 421 fDate->SetFont(&font); 422 fDate->SetHighColor(0, 0, 0); 423 424 LoadMessage(mail); 425 } 426 ResizeTo(Bounds().Width(),y); 427 } 428 429 430 void 431 THeaderView::InitEmailCompletion() 432 { 433 // get boot volume 434 BVolume volume; 435 BVolumeRoster().GetBootVolume(&volume); 436 437 BQuery query; 438 query.SetVolume(&volume); 439 query.SetPredicate("META:email=**"); 440 // Due to R5 BFS bugs, you need two stars, META:email=** for the query. 441 // META:email="*" will just return one entry and stop, same with 442 // META:email=* and a few other variations. Grumble. 443 query.Fetch(); 444 entry_ref ref; 445 446 while (query.GetNextRef (&ref) == B_OK) { 447 BNode file; 448 if (file.SetTo(&ref) == B_OK) { 449 // Add the e-mail address as an auto-complete string. 450 BString email; 451 if (file.ReadAttrString("META:email", &email) >= B_OK) 452 fEmailList.AddChoice(email.String()); 453 454 // Also add the quoted full name as an auto-complete string. Can't 455 // do unquoted since auto-complete isn't that smart, so the user 456 // will have to type a quote mark if he wants to select someone by 457 // name. 458 BString fullName; 459 if (file.ReadAttrString("META:name", &fullName) >= B_OK) { 460 if (email.FindFirst('<') < 0) { 461 email.ReplaceAll('>', '_'); 462 email.Prepend("<"); 463 email.Append(">"); 464 } 465 fullName.ReplaceAll('\"', '_'); 466 fullName.Prepend("\""); 467 fullName << "\" " << email; 468 fEmailList.AddChoice(fullName.String()); 469 } 470 471 // support for 3rd-party People apps. Looks like a job for 472 // multiple keyword (so you can have several e-mail addresses in 473 // one attribute, perhaps comma separated) indices! Which aren't 474 // yet in BFS. 475 for (int16 i = 2; i < 6; i++) { 476 char attr[16]; 477 sprintf(attr, "META:email%d", i); 478 if (file.ReadAttrString(attr, &email) >= B_OK) 479 fEmailList.AddChoice(email.String()); 480 } 481 } 482 } 483 } 484 485 486 void 487 THeaderView::InitGroupCompletion() 488 { 489 // get boot volume 490 BVolume volume; 491 BVolumeRoster().GetBootVolume(&volume); 492 493 // Build a list of all unique groups and the addresses they expand to. 494 BQuery query; 495 query.SetVolume(&volume); 496 query.SetPredicate("META:group=**"); 497 query.Fetch(); 498 499 map<BString *, BString *, CompareBStrings> groupMap; 500 entry_ref ref; 501 BNode file; 502 while (query.GetNextRef(&ref) == B_OK) { 503 if (file.SetTo(&ref) != B_OK) 504 continue; 505 506 BString groups; 507 if (ReadAttrString(&file, "META:group", &groups) < B_OK || groups.Length() == 0) 508 continue; 509 510 BString address; 511 ReadAttrString(&file, "META:email", &address); 512 513 // avoid adding an empty address 514 if (address.Length() == 0) 515 continue; 516 517 char *group = groups.LockBuffer(groups.Length()); 518 char *next = strchr(group, ','); 519 520 for (;;) { 521 if (next) 522 *next = 0; 523 524 while (*group && *group == ' ') 525 group++; 526 527 BString *groupString = new BString(group); 528 BString *addressListString = NULL; 529 530 // nobody is in this group yet, start it off 531 if (groupMap[groupString] == NULL) { 532 addressListString = new BString(*groupString); 533 addressListString->Append(" "); 534 groupMap[groupString] = addressListString; 535 } else { 536 addressListString = groupMap[groupString]; 537 addressListString->Append(", "); 538 delete groupString; 539 } 540 541 // Append the user's address to the end of the string with the 542 // comma separated list of addresses. If not present, add the 543 // < and > brackets around the address. 544 545 if (address.FindFirst ('<') < 0) { 546 address.ReplaceAll ('>', '_'); 547 address.Prepend ("<"); 548 address.Append(">"); 549 } 550 addressListString->Append(address); 551 552 if (!next) 553 break; 554 555 group = next + 1; 556 next = strchr(group, ','); 557 } 558 } 559 560 map<BString *, BString *, CompareBStrings>::iterator iter; 561 for (iter = groupMap.begin(); iter != groupMap.end();) { 562 BString *group = iter->first; 563 BString *addr = iter->second; 564 fEmailList.AddChoice(addr->String()); 565 ++iter; 566 groupMap.erase(group); 567 delete group; 568 delete addr; 569 } 570 } 571 572 573 void 574 THeaderView::MessageReceived(BMessage *msg) 575 { 576 switch (msg->what) { 577 case B_SIMPLE_DATA: 578 { 579 BTextView *textView = dynamic_cast<BTextView *>(Window()->CurrentFocus()); 580 if (dynamic_cast<TTextControl *>(textView->Parent()) != NULL) 581 textView->Parent()->MessageReceived(msg); 582 else { 583 BMessage message(*msg); 584 message.what = REFS_RECEIVED; 585 Window()->PostMessage(&message, Window()); 586 } 587 break; 588 } 589 590 case kMsgFrom: 591 { 592 BMenuItem *item; 593 if (msg->FindPointer("source", (void **)&item) >= B_OK) 594 item->SetMarked(true); 595 596 uint32 chain; 597 if (msg->FindInt32("id",(int32 *)&chain) >= B_OK) 598 fChain = chain; 599 break; 600 } 601 602 case kMsgEncoding: 603 { 604 BMessage message(*msg); 605 int32 charSet; 606 607 if (msg->FindInt32("charset", &charSet) == B_OK) 608 fCharacterSetUserSees = charSet; 609 610 message.what = CHARSET_CHOICE_MADE; 611 message.AddInt32 ("charset", fCharacterSetUserSees); 612 Window()->PostMessage (&message, Window()); 613 break; 614 } 615 } 616 } 617 618 619 void 620 THeaderView::AttachedToWindow() 621 { 622 if (fToMenu) { 623 fToMenu->SetTargetForItems(fTo); 624 fToMenu->SetPredicate("META:email=**"); 625 } 626 if (fCcMenu) { 627 fCcMenu->SetTargetForItems(fCc); 628 fCcMenu->SetPredicate("META:email=**"); 629 } 630 if (fBccMenu) { 631 fBccMenu->SetTargetForItems(fBcc); 632 fBccMenu->SetPredicate("META:email=**"); 633 } 634 if (fTo) 635 fTo->SetTarget(Looper()); 636 if (fSubject) 637 fSubject->SetTarget(Looper()); 638 if (fCc) 639 fCc->SetTarget(Looper()); 640 if (fBcc) 641 fBcc->SetTarget(Looper()); 642 if (fAccount) 643 fAccount->SetTarget(Looper()); 644 if (fAccountMenu) 645 fAccountMenu->SetTargetForItems(this); 646 if (fEncodingMenu) 647 fEncodingMenu->SetTargetForItems(this); 648 649 BBox::AttachedToWindow(); 650 } 651 652 653 status_t 654 THeaderView::LoadMessage(BEmailMessage *mail) 655 { 656 // Set the date on this message 657 const char *dateField = mail->Date(); 658 char string[256]; 659 sprintf(string, "%s %s", kDateLabel, dateField != NULL ? dateField : "Unknown"); 660 fDate->SetText(string); 661 662 // Set contents of header fields 663 if (fIncoming && !fResending) { 664 if (fBcc != NULL) 665 fBcc->SetEnabled(false); 666 667 if (fCc != NULL) 668 fCc->SetEnabled(false); 669 670 if (fAccount != NULL) 671 fAccount->SetEnabled(false); 672 673 if (fAccountTo != NULL) 674 fAccountTo->SetEnabled(false); 675 676 fSubject->SetEnabled(false); 677 fTo->SetEnabled(false); 678 } 679 680 // Set Subject: & From: fields 681 fSubject->SetText(mail->Subject()); 682 fTo->SetText(mail->From()); 683 684 // Set Account/To Field 685 if (fAccountTo != NULL) 686 fAccountTo->SetText(mail->To()); 687 688 if (fAccount != NULL && mail->GetAccountName(string,sizeof(string)) == B_OK) 689 fAccount->SetText(string); 690 691 return B_OK; 692 } 693 694 695 // #pragma mark - TTextControl 696 697 698 TTextControl::TTextControl(BRect rect, char *label, BMessage *msg, 699 bool incoming, bool resending, int32 resizingMode) 700 : BComboBox(rect, "happy", label, msg, resizingMode), 701 fRefDropMenu(NULL) 702 //:BTextControl(rect, "happy", label, "", msg, resizingMode) 703 { 704 strcpy(fLabel, label); 705 fCommand = msg != NULL ? msg->what : 0UL; 706 fIncoming = incoming; 707 fResending = resending; 708 } 709 710 711 void 712 TTextControl::AttachedToWindow() 713 { 714 BFont font = *be_plain_font; 715 BTextView *text; 716 717 SetHighColor(0, 0, 0); 718 // BTextControl::AttachedToWindow(); 719 BComboBox::AttachedToWindow(); 720 font.SetSize(FONT_SIZE); 721 SetFont(&font); 722 723 SetDivider(StringWidth(fLabel) + 6); 724 text = (BTextView *)ChildAt(0); 725 text->SetFont(&font); 726 } 727 728 729 void 730 TTextControl::MessageReceived(BMessage *msg) 731 { 732 switch (msg->what) { 733 case B_SIMPLE_DATA: { 734 if (fIncoming && !fResending) 735 return; 736 737 int32 buttons = -1; 738 BPoint point; 739 if (msg->FindInt32("buttons", &buttons) != B_OK) 740 buttons = B_PRIMARY_MOUSE_BUTTON; 741 742 if (buttons != B_PRIMARY_MOUSE_BUTTON 743 && msg->FindPoint("_drop_point_", &point) != B_OK) 744 return; 745 746 BMessage message(REFS_RECEIVED); 747 bool enclosure = false; 748 BString addressList; 749 // Batch up the addresses to be added, since we can only 750 // insert a few times before deadlocking since inserting 751 // sends a notification message to the window BLooper, 752 // which is busy doing this insert. BeOS message queues 753 // are annoyingly limited in their design. 754 755 entry_ref ref; 756 for (int32 index = 0;msg->FindRef("refs", index, &ref) == B_OK; index++) { 757 BFile file(&ref, B_READ_ONLY); 758 if (file.InitCheck() == B_NO_ERROR) { 759 BNodeInfo info(&file); 760 char type[B_FILE_NAME_LENGTH]; 761 info.GetType(type); 762 763 if (fCommand != SUBJECT_FIELD 764 && !strcmp(type,"application/x-person")) { 765 // add person's E-mail address to the To: field 766 767 BString attr = ""; 768 if (buttons == B_PRIMARY_MOUSE_BUTTON) { 769 if (msg->FindString("attr", &attr) < B_OK) 770 attr = "META:email"; // If not META:email3 etc. 771 } else { 772 BNode node(&ref); 773 node.RewindAttrs(); 774 775 char buffer[B_ATTR_NAME_LENGTH]; 776 777 delete fRefDropMenu; 778 fRefDropMenu = new BPopUpMenu("RecipientMenu"); 779 780 while (node.GetNextAttrName(buffer) == B_OK) { 781 if (strstr(buffer, "email") <= 0) 782 continue; 783 784 attr = buffer; 785 786 BString address; 787 ReadAttrString(&node, buffer, &address); 788 if (address.Length() <= 0) 789 continue; 790 791 BMessage *itemMsg = new BMessage(kMsgAddressChosen); 792 itemMsg->AddString("address", address.String()); 793 itemMsg->AddRef("ref", &ref); 794 795 BMenuItem *item = new BMenuItem(address.String(), 796 itemMsg); 797 fRefDropMenu->AddItem(item); 798 } 799 800 if (fRefDropMenu->CountItems() > 1) { 801 fRefDropMenu->SetTargetForItems(this); 802 fRefDropMenu->Go(point, true, true, true); 803 return; 804 } else { 805 delete fRefDropMenu; 806 fRefDropMenu = NULL; 807 } 808 } 809 810 BString email; 811 ReadAttrString(&file,attr.String(),&email); 812 813 // we got something... 814 if (email.Length() > 0) { 815 // see if we can get a username as well 816 BString name; 817 ReadAttrString(&file, "META:name", &name); 818 819 BString address; 820 if (name.Length() == 0) { 821 // if we have no Name, just use the email address 822 address = email; 823 } else { 824 // otherwise, pretty-format it 825 address << "\"" << name << "\" <" << email << ">"; 826 } 827 828 if (addressList.Length() > 0) 829 addressList << ", "; 830 addressList << address; 831 } 832 } else { 833 enclosure = true; 834 message.AddRef("refs", &ref); 835 } 836 } 837 } 838 839 if (addressList.Length() > 0) { 840 BTextView *textView = TextView(); 841 int end = textView->TextLength(); 842 if (end != 0) { 843 textView->Select(end, end); 844 textView->Insert(", "); 845 } 846 textView->Insert(addressList.String()); 847 } 848 849 if (enclosure) 850 Window()->PostMessage(&message, Window()); 851 break; 852 } 853 854 case M_SELECT: 855 { 856 BTextView *textView = (BTextView *)ChildAt(0); 857 if (textView != NULL) 858 textView->Select(0, textView->TextLength()); 859 break; 860 } 861 862 case kMsgAddressChosen: { 863 BString display; 864 BString address; 865 entry_ref ref; 866 867 if (msg->FindString("address", &address) != B_OK 868 || msg->FindRef("ref", &ref) != B_OK) 869 return; 870 871 if (address.Length() > 0) { 872 BString name; 873 BNode node(&ref); 874 875 display = address; 876 877 ReadAttrString(&node, "META:name", &name); 878 if (name.Length() > 0) { 879 display = ""; 880 display << "\"" << name << "\" <" << address << ">"; 881 } 882 883 BTextView *textView = TextView(); 884 int end = textView->TextLength(); 885 if (end != 0) { 886 textView->Select(end, end); 887 textView->Insert(", "); 888 } 889 textView->Insert(display.String()); 890 } 891 break; 892 } 893 894 default: 895 // BTextControl::MessageReceived(msg); 896 BComboBox::MessageReceived(msg); 897 } 898 } 899 900 901 bool 902 TTextControl::HasFocus() 903 { 904 return TextView()->IsFocus(); 905 } 906 907 908 // #pragma mark - QPopupMenu 909 910 911 QPopupMenu::QPopupMenu(const char *title) 912 : QueryMenu(title, true), 913 fGroups(0) 914 { 915 } 916 917 918 void 919 QPopupMenu::AddPersonItem(const entry_ref *ref, ino_t node, BString &name, 920 BString &email, const char *attr, BMenu *groupMenu, BMenuItem *superItem) 921 { 922 BString label; 923 BString sortKey; 924 // For alphabetical order sorting, usually last name. 925 926 // if we have no Name, just use the email address 927 if (name.Length() == 0) { 928 label = email; 929 sortKey = email; 930 } else { 931 // otherwise, pretty-format it 932 label << name << " (" << email << ")"; 933 934 // Extract the last name (last word in the name), 935 // removing trailing and leading spaces. 936 const char *nameStart = name.String(); 937 const char *string = nameStart + strlen(nameStart) - 1; 938 const char *wordEnd; 939 940 while (string >= nameStart && isspace(*string)) 941 string--; 942 wordEnd = string + 1; // Points to just after last word. 943 while (string >= nameStart && !isspace(*string)) 944 string--; 945 string++; // Point to first letter in the word. 946 if (wordEnd > string) 947 sortKey.SetTo(string, wordEnd - string); 948 else // Blank name, pretend that the last name is after it. 949 string = nameStart + strlen(nameStart); 950 951 // Append the first names to the end, so that people with the same last 952 // name get sorted by first name. Note no space between the end of the 953 // last name and the start of the first names, but that shouldn't 954 // matter for sorting. 955 sortKey.Append(nameStart, string - nameStart); 956 } 957 958 // The target (a TTextControl) will examine all the People files specified 959 // and add the emails and names to the string it is displaying (same code 960 // is used for drag and drop of People files). 961 BMessage *msg = new BMessage(B_SIMPLE_DATA); 962 msg->AddRef("refs", ref); 963 msg->AddInt64("node", node); 964 if (attr) // For nonstandard e-mail attributes, like META:email3 965 msg->AddString("attr", attr); 966 msg->AddString("sortkey", sortKey); 967 968 BMenuItem *newItem = new BMenuItem(label.String(), msg); 969 if (fTargetHandler) 970 newItem->SetTarget(fTargetHandler); 971 972 // If no group, just add it to ourself; else add it to group menu 973 BMenu *parentMenu = groupMenu ? groupMenu : this; 974 if (groupMenu) { 975 // Add ref to group super item. 976 BMessage *superMsg = superItem->Message(); 977 superMsg->AddRef("refs", ref); 978 } 979 980 // Add it to the appropriate menu. Use alphabetical order by sortKey to 981 // insert it in the right spot (a dumb linear search so this will be slow). 982 // Start searching from the end of the menu, since the main menu includes 983 // all the groups at the top and we don't want to mix it in with them. 984 // Thus the search starts at the bottom and ends when we hit a separator 985 // line or the top of the menu. 986 987 int32 index = parentMenu->CountItems(); 988 while (index-- > 0) { 989 BMenuItem *item = parentMenu->ItemAt(index); 990 if (item == NULL || dynamic_cast<BSeparatorItem *>(item) != NULL) 991 break; 992 993 BMessage *message = item->Message(); 994 BString key; 995 996 // Stop when testKey < sortKey. 997 if (message != NULL 998 && message->FindString("sortkey", &key) == B_OK 999 && ICompare(key, sortKey) < 0) 1000 break; 1001 } 1002 1003 if (!parentMenu->AddItem(newItem, index + 1)) { 1004 fprintf (stderr, "QPopupMenu::AddPersonItem: Unable to add menu " 1005 "item \"%s\" at index %ld.\n", sortKey.String(), index + 1); 1006 delete newItem; 1007 } 1008 } 1009 1010 1011 void 1012 QPopupMenu::EntryCreated(const entry_ref &ref, ino_t node) 1013 { 1014 BNode file; 1015 if (file.SetTo(&ref) < B_OK) 1016 return; 1017 1018 // Make sure the pop-up menu is ready for additions. Need a bunch of 1019 // groups at the top, a divider line, and miscellaneous people added below 1020 // the line. 1021 1022 int32 items = CountItems(); 1023 if (!items) 1024 AddSeparatorItem(); 1025 1026 // Does the file have a group attribute? OK to have none. 1027 BString groups; 1028 const char *kNoGroup = "NoGroup!"; 1029 ReadAttrString(&file, "META:group", &groups); 1030 if (groups.Length() <= 0) 1031 groups = kNoGroup; 1032 1033 // Add the e-mail address to the all people group. Then add it to all the 1034 // group menus that it exists in (based on the comma separated list of 1035 // groups from the People file), optionally making the group menu if it 1036 // doesn't exist. If it's in the special NoGroup! list, then add it below 1037 // the groups. 1038 1039 bool allPeopleGroupDone = false; 1040 BMenu *groupMenu; 1041 do { 1042 BString group; 1043 1044 if (!allPeopleGroupDone) { 1045 // Create the default group for all people, if it doesn't exist yet. 1046 group = "All People"; 1047 allPeopleGroupDone = true; 1048 } else { 1049 // Break out the next group from the comma separated string. 1050 int32 comma; 1051 if ((comma = groups.FindFirst(',')) > 0) { 1052 groups.MoveInto(group, 0, comma); 1053 groups.Remove(0, 1); 1054 } else 1055 group.Adopt(groups); 1056 } 1057 1058 // trim white spaces 1059 int32 i = 0; 1060 for (i = 0; isspace(group.ByteAt(i)); i++) {} 1061 if (i) 1062 group.Remove(0, i); 1063 for (i = group.Length() - 1; isspace(group.ByteAt(i)); i--) {} 1064 group.Truncate(i + 1); 1065 1066 groupMenu = NULL; 1067 BMenuItem *superItem = NULL; // Corresponding item for group menu. 1068 1069 if (group.Length() > 0 && group != kNoGroup) { 1070 BMenu *sub; 1071 1072 // Look for submenu with label == group name 1073 for (int32 i = 0; i < items; i++) { 1074 if ((sub = SubmenuAt(i)) != NULL) { 1075 superItem = sub->Superitem(); 1076 if (!strcmp(superItem->Label(), group.String())) { 1077 groupMenu = sub; 1078 i++; 1079 break; 1080 } 1081 } 1082 } 1083 1084 // If no submenu, create one 1085 if (!groupMenu) { 1086 // Find where it should go (alphabetical) 1087 int32 mindex = 0; 1088 for (; mindex < fGroups; mindex++) { 1089 if (strcmp(ItemAt(mindex)->Label(), group.String()) > 0) 1090 break; 1091 } 1092 1093 groupMenu = new BMenu(group.String()); 1094 groupMenu->SetFont(be_plain_font); 1095 AddItem(groupMenu, mindex); 1096 1097 superItem = groupMenu->Superitem(); 1098 superItem->SetMessage(new BMessage(B_SIMPLE_DATA)); 1099 if (fTargetHandler) 1100 superItem->SetTarget(fTargetHandler); 1101 1102 fGroups++; 1103 } 1104 } 1105 1106 BString name; 1107 ReadAttrString(&file, "META:name", &name); 1108 1109 BString email; 1110 ReadAttrString(&file, "META:email", &email); 1111 1112 if (email.Length() != 0 || name.Length() != 0) 1113 AddPersonItem(&ref, node, name, email, NULL, groupMenu, superItem); 1114 1115 // support for 3rd-party People apps 1116 for (int16 i = 2; i < 6; i++) { 1117 char attr[16]; 1118 sprintf(attr, "META:email%d", i); 1119 if (ReadAttrString(&file, attr, &email) >= B_OK && email.Length() > 0) 1120 AddPersonItem(&ref, node, name, email, attr, groupMenu, superItem); 1121 } 1122 } while (groups.Length() > 0); 1123 } 1124 1125 1126 void 1127 QPopupMenu::EntryRemoved(ino_t /*node*/) 1128 { 1129 } 1130