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