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_TRANSLATION_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 bool resending, uint32 defaultCharacterSet, int32 defaultAccount) 137 : 138 BBox(rect, "m_header", B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW, B_NO_BORDER), 139 140 fAccountMenu(NULL), 141 fEncodingMenu(NULL), 142 fAccountID(defaultAccount), 143 fAccountTo(NULL), 144 fAccount(NULL), 145 fBcc(NULL), 146 fCc(NULL), 147 fSubject(NULL), 148 fTo(NULL), 149 fDateLabel(NULL), 150 fDate(NULL), 151 fIncoming(incoming), 152 fCharacterSetUserSees(defaultCharacterSet), 153 fResending(resending), 154 fBccMenu(NULL), 155 fCcMenu(NULL), 156 fToMenu(NULL), 157 fEmailList(NULL) 158 { 159 BMenuField* field; 160 BMessage* msg; 161 162 float x = StringWidth( /* The longest title string in the header area */ 163 B_TRANSLATE("Attachments: ")) + 9; 164 float y = TO_FIELD_V; 165 166 BMenuBar* dummy = new BMenuBar(BRect(0, 0, 100, 15), "Dummy"); 167 AddChild(dummy); 168 float width, menuBarHeight; 169 dummy->GetPreferredSize(&width, &menuBarHeight); 170 dummy->RemoveSelf(); 171 delete dummy; 172 173 float menuFieldHeight = menuBarHeight + 6; 174 float controlHeight = menuBarHeight + floorf(be_plain_font->Size() / 1.15); 175 176 if (!fIncoming) { 177 InitEmailCompletion(); 178 InitGroupCompletion(); 179 } 180 181 // Prepare the character set selection pop-up menu (we tell the user that 182 // it is the Encoding menu, even though it is really the character set). 183 // It may appear in the first line, to the right of the From box if the 184 // user is reading an e-mail. It appears on the second line, to the right 185 // of the e-mail account menu, if the user is composing a message. It lets 186 // the user quickly select a character set different from the application 187 // wide default one, and also shows them which character set is active. If 188 // you are reading a message, you also see an item that says "Automatic" 189 // for automatic decoding character set choice. It can slide around as the 190 // window is resized when viewing a message, but not when composing 191 // (because the adjacent pop-up menu can't resize dynamically due to a BeOS 192 // bug). 193 194 float widestCharacterSet = 0; 195 bool markedCharSet = false; 196 BMenuItem* item; 197 198 fEncodingMenu = new BPopUpMenu(B_EMPTY_STRING); 199 200 BCharacterSetRoster roster; 201 BCharacterSet charset; 202 while (roster.GetNextCharacterSet(&charset) == B_OK) { 203 BString name(charset.GetPrintName()); 204 const char* mime = charset.GetMIMEName(); 205 if (mime) 206 name << " (" << mime << ")"; 207 208 uint32 convertID; 209 if (mime == NULL || strcasecmp(mime, "UTF-8") != 0) 210 convertID = charset.GetConversionID(); 211 else 212 convertID = B_MAIL_UTF8_CONVERSION; 213 214 msg = new BMessage(kMsgEncoding); 215 msg->AddInt32("charset", convertID); 216 fEncodingMenu->AddItem(item = new BMenuItem(name.String(), msg)); 217 if (convertID == fCharacterSetUserSees && !markedCharSet) { 218 item->SetMarked(true); 219 markedCharSet = true; 220 } 221 if (StringWidth(name.String()) > widestCharacterSet) 222 widestCharacterSet = StringWidth(name.String()); 223 } 224 225 msg = new BMessage(kMsgEncoding); 226 msg->AddInt32("charset", B_MAIL_US_ASCII_CONVERSION); 227 fEncodingMenu->AddItem(item = new BMenuItem("US-ASCII", msg)); 228 if (fCharacterSetUserSees == B_MAIL_US_ASCII_CONVERSION && !markedCharSet) { 229 item->SetMarked(true); 230 markedCharSet = true; 231 } 232 233 if (!resending && fIncoming) { 234 // reading a message, display the Automatic item 235 fEncodingMenu->AddSeparatorItem(); 236 msg = new BMessage(kMsgEncoding); 237 msg->AddInt32("charset", B_MAIL_NULL_CONVERSION); 238 fEncodingMenu->AddItem(item = new BMenuItem(B_TRANSLATE("Automatic"), msg)); 239 if (!markedCharSet) 240 item->SetMarked(true); 241 } 242 243 // First line of the header, From for reading e-mails (includes the 244 // character set choice at the right), To when composing (nothing else in 245 // the row). 246 247 BRect r; 248 char string[20]; 249 if (fIncoming && !resending) { 250 // Set up the character set pop-up menu on the right of "To" box. 251 r.Set (windowRect.Width() - widestCharacterSet - 252 StringWidth (B_TRANSLATE("Decoding:")) - 2 * SEPARATOR_MARGIN, 253 y - 2, windowRect.Width() - SEPARATOR_MARGIN, 254 y + menuFieldHeight); 255 field = new BMenuField (r, "decoding", B_TRANSLATE("Decoding:"), 256 fEncodingMenu, true /* fixedSize */, 257 B_FOLLOW_TOP | B_FOLLOW_RIGHT, 258 B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); 259 field->SetDivider(field->StringWidth(B_TRANSLATE("Decoding:")) + 5); 260 AddChild(field); 261 r.Set(SEPARATOR_MARGIN, y, 262 field->Frame().left - SEPARATOR_MARGIN, y + menuFieldHeight); 263 sprintf(string, B_TRANSLATE("From:")); 264 } else { 265 r.Set(x - 12, y, windowRect.Width() - SEPARATOR_MARGIN, 266 y + menuFieldHeight); 267 string[0] = 0; 268 } 269 270 y += controlHeight; 271 fTo = new TTextControl(r, string, new BMessage(TO_FIELD), fIncoming, 272 resending, B_FOLLOW_LEFT_RIGHT); 273 fTo->SetFilter(mail_to_filter); 274 275 if (!fIncoming || resending) { 276 fTo->SetChoiceList(&fEmailList); 277 fTo->SetAutoComplete(true); 278 } else { 279 fTo->SetDivider(x - 12 - SEPARATOR_MARGIN); 280 fTo->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); 281 } 282 283 AddChild(fTo); 284 msg = new BMessage(FIELD_CHANGED); 285 msg->AddInt32("bitmask", FIELD_TO); 286 fTo->SetModificationMessage(msg); 287 288 if (!fIncoming || resending) { 289 r.right = r.left - 5; 290 r.left = r.right - ceilf(be_plain_font->StringWidth( 291 B_TRANSLATE("To:")) + 25); 292 r.top -= 1; 293 fToMenu = new QPopupMenu(B_TRANSLATE("To:")); 294 field = new BMenuField(r, "", "", fToMenu, true, 295 B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW); 296 field->SetDivider(0.0); 297 field->SetEnabled(true); 298 AddChild(field); 299 } 300 301 // "From:" accounts Menu and Encoding Menu. 302 if (!fIncoming || resending) { 303 // Put the character set box on the right of the From field. 304 r.Set(windowRect.Width() - widestCharacterSet - 305 StringWidth(B_TRANSLATE("Encoding:")) - 2 * SEPARATOR_MARGIN, 306 y - 2, windowRect.Width() - SEPARATOR_MARGIN, y + menuFieldHeight); 307 BMenuField* encodingField = new BMenuField(r, "encoding", 308 B_TRANSLATE("Encoding:"), fEncodingMenu, true /* fixedSize */, 309 B_FOLLOW_TOP | B_FOLLOW_RIGHT, 310 B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); 311 encodingField->SetDivider(encodingField->StringWidth( 312 B_TRANSLATE("Encoding:")) + 5); 313 AddChild(encodingField); 314 315 field = encodingField; 316 317 // And now the "from account" pop-up menu, on the left side, taking the 318 // remaining space. 319 320 fAccountMenu = new BPopUpMenu(B_EMPTY_STRING); 321 322 BMailAccounts accounts; 323 bool marked = false; 324 for (int32 i = 0; i < accounts.CountAccounts(); i++) { 325 BMailAccountSettings* account = accounts.AccountAt(i); 326 BString name = account->Name(); 327 name << ": " << account->RealName() << " <" 328 << account->ReturnAddress() << ">"; 329 330 msg = new BMessage(kMsgFrom); 331 BMenuItem *item = new BMenuItem(name, msg); 332 333 msg->AddInt32("id", account->AccountID()); 334 335 if (defaultAccount == account->AccountID()) { 336 item->SetMarked(true); 337 marked = true; 338 } 339 fAccountMenu->AddItem(item); 340 } 341 342 if (!marked) { 343 BMenuItem *item = fAccountMenu->ItemAt(0); 344 if (item != NULL) { 345 item->SetMarked(true); 346 fAccountID = item->Message()->FindInt32("id"); 347 } else { 348 fAccountMenu->AddItem( 349 item = new BMenuItem(B_TRANSLATE("<none>"), NULL)); 350 item->SetEnabled(false); 351 fAccountID = ~0UL; 352 } 353 // default account is invalid, set to marked 354 // TODO: do this differently, no casting and knowledge 355 // of TMailApp here.... 356 if (TMailApp* app = dynamic_cast<TMailApp*>(be_app)) 357 app->SetDefaultAccount(fAccountID); 358 } 359 360 r.Set(SEPARATOR_MARGIN, y - 2, 361 field->Frame().left - SEPARATOR_MARGIN, y + menuFieldHeight); 362 field = new BMenuField(r, "account", B_TRANSLATE("From:"), 363 fAccountMenu, true /* fixedSize */, 364 B_FOLLOW_TOP | B_FOLLOW_LEFT_RIGHT, 365 B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); 366 AddChild(field, encodingField); 367 field->SetDivider(x - 12 - SEPARATOR_MARGIN + kMenuFieldDividerOffset); 368 field->SetAlignment(B_ALIGN_RIGHT); 369 y += controlHeight; 370 } else { 371 // To: account 372 bool account = BMailAccounts().CountAccounts() > 0; 373 374 r.Set(SEPARATOR_MARGIN, y, 375 windowRect.Width() - SEPARATOR_MARGIN, y + menuFieldHeight); 376 if (account) 377 r.right -= SEPARATOR_MARGIN + ACCOUNT_FIELD_WIDTH; 378 fAccountTo = new TTextControl(r, B_TRANSLATE("To:"), NULL, fIncoming, 379 false, B_FOLLOW_LEFT_RIGHT); 380 fAccountTo->SetEnabled(false); 381 fAccountTo->SetDivider(x - 12 - SEPARATOR_MARGIN); 382 fAccountTo->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); 383 AddChild(fAccountTo); 384 385 if (account) { 386 r.left = r.right + 6; r.right = windowRect.Width() - SEPARATOR_MARGIN; 387 fAccount = new TTextControl(r, B_TRANSLATE("Account:"), NULL, 388 fIncoming, false, B_FOLLOW_RIGHT | B_FOLLOW_TOP); 389 fAccount->SetEnabled(false); 390 AddChild(fAccount); 391 } 392 y += controlHeight; 393 } 394 395 --y; 396 r.Set(SEPARATOR_MARGIN, y, 397 windowRect.Width() - SEPARATOR_MARGIN, y + menuFieldHeight); 398 y += controlHeight; 399 fSubject = new TTextControl(r, B_TRANSLATE("Subject:"), 400 new BMessage(SUBJECT_FIELD),fIncoming, false, B_FOLLOW_LEFT_RIGHT); 401 AddChild(fSubject); 402 (msg = new BMessage(FIELD_CHANGED))->AddInt32("bitmask", FIELD_SUBJECT); 403 fSubject->SetModificationMessage(msg); 404 fSubject->SetDivider(x - 12 - SEPARATOR_MARGIN); 405 fSubject->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); 406 if (fResending) 407 fSubject->SetEnabled(false); 408 409 --y; 410 411 if (!fIncoming) { 412 r.Set(x - 12, y, CC_FIELD_H + CC_FIELD_WIDTH, y + menuFieldHeight); 413 fCc = new TTextControl(r, "", new BMessage(CC_FIELD), fIncoming, false); 414 fCc->SetFilter(mail_to_filter); 415 fCc->SetChoiceList(&fEmailList); 416 fCc->SetAutoComplete(true); 417 AddChild(fCc); 418 (msg = new BMessage(FIELD_CHANGED))->AddInt32("bitmask", FIELD_CC); 419 fCc->SetModificationMessage(msg); 420 421 r.right = r.left - 5; 422 r.left = r.right - ceilf(be_plain_font->StringWidth( 423 B_TRANSLATE("Cc:")) + 25); 424 r.top -= 1; 425 fCcMenu = new QPopupMenu(B_TRANSLATE("Cc:")); 426 field = new BMenuField(r, "", "", fCcMenu, true, 427 B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW); 428 429 field->SetDivider(0.0); 430 field->SetEnabled(true); 431 AddChild(field); 432 433 r.Set(BCC_FIELD_H + be_plain_font->StringWidth(B_TRANSLATE("Bcc:")), y, 434 windowRect.Width() - SEPARATOR_MARGIN, y + menuFieldHeight); 435 y += controlHeight; 436 fBcc = new TTextControl(r, "", new BMessage(BCC_FIELD), 437 fIncoming, false, B_FOLLOW_LEFT_RIGHT); 438 fBcc->SetFilter(mail_to_filter); 439 fBcc->SetChoiceList(&fEmailList); 440 fBcc->SetAutoComplete(true); 441 AddChild(fBcc); 442 (msg = new BMessage(FIELD_CHANGED))->AddInt32("bitmask", FIELD_BCC); 443 fBcc->SetModificationMessage(msg); 444 445 r.right = r.left - 5; 446 r.left = r.right - ceilf(be_plain_font->StringWidth( 447 B_TRANSLATE("Bcc:")) + 25); 448 r.top -= 1; 449 fBccMenu = new QPopupMenu(B_TRANSLATE("Bcc:")); 450 field = new BMenuField(r, "", "", fBccMenu, true, 451 B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW); 452 field->SetDivider(0.0); 453 field->SetEnabled(true); 454 AddChild(field); 455 } else { 456 y -= SEPARATOR_MARGIN; 457 r.Set(SEPARATOR_MARGIN, y, x - 12 - 1, y + menuFieldHeight); 458 fDateLabel = new BStringView(r, "", kDateLabel); 459 fDateLabel->SetAlignment(B_ALIGN_RIGHT); 460 AddChild(fDateLabel); 461 fDateLabel->SetHighColor(0, 0, 0); 462 463 r.Set(r.right + 9, y, windowRect.Width() - SEPARATOR_MARGIN, 464 y + menuFieldHeight); 465 fDate = new BStringView(r, "", ""); 466 AddChild(fDate); 467 fDate->SetHighColor(0, 0, 0); 468 469 y += controlHeight + 5; 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 (file.ReadAttrString("META:group", &groups) < B_OK || groups.Length() == 0) 553 continue; 554 555 BString address; 556 file.ReadAttrString("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 645 BMessage message(FIELD_CHANGED); 646 // field doesn't matter; no special processing for this field 647 // it's just to turn on the save button 648 message.AddInt32("bitmask", 0); 649 Window()->PostMessage(&message, Window()); 650 651 break; 652 } 653 654 case kMsgEncoding: 655 { 656 BMessage message(*msg); 657 int32 charSet; 658 659 if (msg->FindInt32("charset", &charSet) == B_OK) 660 fCharacterSetUserSees = charSet; 661 662 message.what = CHARSET_CHOICE_MADE; 663 message.AddInt32 ("charset", fCharacterSetUserSees); 664 Window()->PostMessage (&message, Window()); 665 666 BMessage message2(FIELD_CHANGED); 667 // field doesn't matter; no special processing for this field 668 // it's just to turn on the save button 669 message2.AddInt32("bitmask", 0); 670 Window()->PostMessage(&message2, Window()); 671 672 break; 673 } 674 } 675 } 676 677 678 void 679 THeaderView::AttachedToWindow() 680 { 681 if (fToMenu) { 682 fToMenu->SetTargetForItems(fTo); 683 fToMenu->SetPredicate("META:email=**"); 684 } 685 if (fCcMenu) { 686 fCcMenu->SetTargetForItems(fCc); 687 fCcMenu->SetPredicate("META:email=**"); 688 } 689 if (fBccMenu) { 690 fBccMenu->SetTargetForItems(fBcc); 691 fBccMenu->SetPredicate("META:email=**"); 692 } 693 if (fTo) 694 fTo->SetTarget(Looper()); 695 if (fSubject) 696 fSubject->SetTarget(Looper()); 697 if (fCc) 698 fCc->SetTarget(Looper()); 699 if (fBcc) 700 fBcc->SetTarget(Looper()); 701 if (fAccount) 702 fAccount->SetTarget(Looper()); 703 if (fAccountMenu) 704 fAccountMenu->SetTargetForItems(this); 705 if (fEncodingMenu) 706 fEncodingMenu->SetTargetForItems(this); 707 708 BBox::AttachedToWindow(); 709 } 710 711 712 status_t 713 THeaderView::LoadMessage(BEmailMessage *mail) 714 { 715 // Set the date on this message 716 const char *dateField = mail->Date(); 717 char string[256]; 718 sprintf(string, "%s", dateField != NULL ? dateField : "Unknown"); 719 fDate->SetText(string); 720 721 // Set contents of header fields 722 if (fIncoming && !fResending) { 723 if (fBcc != NULL) 724 fBcc->SetEnabled(false); 725 726 if (fCc != NULL) 727 fCc->SetEnabled(false); 728 729 if (fAccount != NULL) 730 fAccount->SetEnabled(false); 731 732 if (fAccountTo != NULL) 733 fAccountTo->SetEnabled(false); 734 735 fSubject->SetEnabled(false); 736 fTo->SetEnabled(false); 737 } 738 739 // Set Subject: & From: fields 740 fSubject->SetText(mail->Subject()); 741 fTo->SetText(mail->From()); 742 743 // Set Account/To Field 744 if (fAccountTo != NULL) 745 fAccountTo->SetText(mail->To()); 746 747 BString accountName; 748 if (fAccount != NULL && mail->GetAccountName(accountName) == B_OK) 749 fAccount->SetText(accountName); 750 751 return B_OK; 752 } 753 754 755 // #pragma mark - TTextControl 756 757 758 TTextControl::TTextControl(BRect rect, const char *label, BMessage *msg, 759 bool incoming, bool resending, int32 resizingMode) 760 : BComboBox(rect, "happy", label, msg, resizingMode), 761 fRefDropMenu(NULL) 762 //:BTextControl(rect, "happy", label, "", msg, resizingMode) 763 { 764 strcpy(fLabel, label); 765 fCommand = msg != NULL ? msg->what : 0UL; 766 fIncoming = incoming; 767 fResending = resending; 768 } 769 770 771 void 772 TTextControl::AttachedToWindow() 773 { 774 SetHighColor(0, 0, 0); 775 // BTextControl::AttachedToWindow(); 776 BComboBox::AttachedToWindow(); 777 778 SetDivider(Divider() + kTextControlDividerOffset); 779 } 780 781 782 void 783 TTextControl::MessageReceived(BMessage *msg) 784 { 785 switch (msg->what) { 786 case B_SIMPLE_DATA: { 787 if (fIncoming && !fResending) 788 return; 789 790 int32 buttons = -1; 791 BPoint point; 792 if (msg->FindInt32("buttons", &buttons) != B_OK) 793 buttons = B_PRIMARY_MOUSE_BUTTON; 794 795 if (buttons != B_PRIMARY_MOUSE_BUTTON 796 && msg->FindPoint("_drop_point_", &point) != B_OK) 797 return; 798 799 BMessage message(REFS_RECEIVED); 800 bool enclosure = false; 801 BString addressList; 802 // Batch up the addresses to be added, since we can only 803 // insert a few times before deadlocking since inserting 804 // sends a notification message to the window BLooper, 805 // which is busy doing this insert. BeOS message queues 806 // are annoyingly limited in their design. 807 808 entry_ref ref; 809 for (int32 index = 0;msg->FindRef("refs", index, &ref) == B_OK; index++) { 810 BFile file(&ref, B_READ_ONLY); 811 if (file.InitCheck() == B_NO_ERROR) { 812 BNodeInfo info(&file); 813 char type[B_FILE_NAME_LENGTH]; 814 info.GetType(type); 815 816 if (fCommand != SUBJECT_FIELD 817 && !strcmp(type,"application/x-person")) { 818 // add person's E-mail address to the To: field 819 820 BString attr = ""; 821 if (buttons == B_PRIMARY_MOUSE_BUTTON) { 822 if (msg->FindString("attr", &attr) < B_OK) 823 attr = "META:email"; // If not META:email3 etc. 824 } else { 825 BNode node(&ref); 826 node.RewindAttrs(); 827 828 char buffer[B_ATTR_NAME_LENGTH]; 829 830 delete fRefDropMenu; 831 fRefDropMenu = new BPopUpMenu("RecipientMenu"); 832 833 while (node.GetNextAttrName(buffer) == B_OK) { 834 if (strstr(buffer, "email") <= 0) 835 continue; 836 837 attr = buffer; 838 839 BString address; 840 node.ReadAttrString(buffer, &address); 841 if (address.Length() <= 0) 842 continue; 843 844 BMessage *itemMsg = new BMessage(kMsgAddressChosen); 845 itemMsg->AddString("address", address.String()); 846 itemMsg->AddRef("ref", &ref); 847 848 BMenuItem *item = new BMenuItem(address.String(), 849 itemMsg); 850 fRefDropMenu->AddItem(item); 851 } 852 853 if (fRefDropMenu->CountItems() > 1) { 854 fRefDropMenu->SetTargetForItems(this); 855 fRefDropMenu->Go(point, true, true, true); 856 return; 857 } else { 858 delete fRefDropMenu; 859 fRefDropMenu = NULL; 860 } 861 } 862 863 BString email; 864 file.ReadAttrString(attr.String(), &email); 865 866 // we got something... 867 if (email.Length() > 0) { 868 // see if we can get a username as well 869 BString name; 870 file.ReadAttrString("META:name", &name); 871 872 BString address; 873 if (name.Length() == 0) { 874 // if we have no Name, just use the email address 875 address = email; 876 } else { 877 // otherwise, pretty-format it 878 address << "\"" << name << "\" <" << email << ">"; 879 } 880 881 if (addressList.Length() > 0) 882 addressList << ", "; 883 addressList << address; 884 } 885 } else { 886 enclosure = true; 887 message.AddRef("refs", &ref); 888 } 889 } 890 } 891 892 if (addressList.Length() > 0) { 893 BTextView *textView = TextView(); 894 int end = textView->TextLength(); 895 if (end != 0) { 896 textView->Select(end, end); 897 textView->Insert(", "); 898 } 899 textView->Insert(addressList.String()); 900 } 901 902 if (enclosure) 903 Window()->PostMessage(&message, Window()); 904 break; 905 } 906 907 case M_SELECT: 908 { 909 BTextView *textView = (BTextView *)ChildAt(0); 910 if (textView != NULL) 911 textView->Select(0, textView->TextLength()); 912 break; 913 } 914 915 case kMsgAddressChosen: { 916 BString display; 917 BString address; 918 entry_ref ref; 919 920 if (msg->FindString("address", &address) != B_OK 921 || msg->FindRef("ref", &ref) != B_OK) 922 return; 923 924 if (address.Length() > 0) { 925 BString name; 926 BNode node(&ref); 927 928 display = address; 929 930 node.ReadAttrString("META:name", &name); 931 if (name.Length() > 0) { 932 display = ""; 933 display << "\"" << name << "\" <" << address << ">"; 934 } 935 936 BTextView *textView = TextView(); 937 int end = textView->TextLength(); 938 if (end != 0) { 939 textView->Select(end, end); 940 textView->Insert(", "); 941 } 942 textView->Insert(display.String()); 943 } 944 break; 945 } 946 947 default: 948 // BTextControl::MessageReceived(msg); 949 BComboBox::MessageReceived(msg); 950 } 951 } 952 953 954 bool 955 TTextControl::HasFocus() 956 { 957 return TextView()->IsFocus(); 958 } 959 960 961 // #pragma mark - QPopupMenu 962 963 964 QPopupMenu::QPopupMenu(const char *title) 965 : QueryMenu(title, true), 966 fGroups(0) 967 { 968 } 969 970 971 void 972 QPopupMenu::AddPersonItem(const entry_ref *ref, ino_t node, BString &name, 973 BString &email, const char *attr, BMenu *groupMenu, BMenuItem *superItem) 974 { 975 BString label; 976 BString sortKey; 977 // For alphabetical order sorting, usually last name. 978 979 // if we have no Name, just use the email address 980 if (name.Length() == 0) { 981 label = email; 982 sortKey = email; 983 } else { 984 // otherwise, pretty-format it 985 label << name << " (" << email << ")"; 986 987 // Extract the last name (last word in the name), 988 // removing trailing and leading spaces. 989 const char *nameStart = name.String(); 990 const char *string = nameStart + strlen(nameStart) - 1; 991 const char *wordEnd; 992 993 while (string >= nameStart && isspace(*string)) 994 string--; 995 wordEnd = string + 1; // Points to just after last word. 996 while (string >= nameStart && !isspace(*string)) 997 string--; 998 string++; // Point to first letter in the word. 999 if (wordEnd > string) 1000 sortKey.SetTo(string, wordEnd - string); 1001 else // Blank name, pretend that the last name is after it. 1002 string = nameStart + strlen(nameStart); 1003 1004 // Append the first names to the end, so that people with the same last 1005 // name get sorted by first name. Note no space between the end of the 1006 // last name and the start of the first names, but that shouldn't 1007 // matter for sorting. 1008 sortKey.Append(nameStart, string - nameStart); 1009 } 1010 1011 // The target (a TTextControl) will examine all the People files specified 1012 // and add the emails and names to the string it is displaying (same code 1013 // is used for drag and drop of People files). 1014 BMessage *msg = new BMessage(B_SIMPLE_DATA); 1015 msg->AddRef("refs", ref); 1016 msg->AddInt64("node", node); 1017 if (attr) // For nonstandard e-mail attributes, like META:email3 1018 msg->AddString("attr", attr); 1019 msg->AddString("sortkey", sortKey); 1020 1021 BMenuItem *newItem = new BMenuItem(label.String(), msg); 1022 if (fTargetHandler) 1023 newItem->SetTarget(fTargetHandler); 1024 1025 // If no group, just add it to ourself; else add it to group menu 1026 BMenu *parentMenu = groupMenu ? groupMenu : this; 1027 if (groupMenu) { 1028 // Add ref to group super item. 1029 BMessage *superMsg = superItem->Message(); 1030 superMsg->AddRef("refs", ref); 1031 } 1032 1033 // Add it to the appropriate menu. Use alphabetical order by sortKey to 1034 // insert it in the right spot (a dumb linear search so this will be slow). 1035 // Start searching from the end of the menu, since the main menu includes 1036 // all the groups at the top and we don't want to mix it in with them. 1037 // Thus the search starts at the bottom and ends when we hit a separator 1038 // line or the top of the menu. 1039 1040 int32 index = parentMenu->CountItems(); 1041 while (index-- > 0) { 1042 BMenuItem *item = parentMenu->ItemAt(index); 1043 if (item == NULL || dynamic_cast<BSeparatorItem *>(item) != NULL) 1044 break; 1045 1046 BMessage *message = item->Message(); 1047 BString key; 1048 1049 // Stop when testKey < sortKey. 1050 if (message != NULL 1051 && message->FindString("sortkey", &key) == B_OK 1052 && ICompare(key, sortKey) < 0) 1053 break; 1054 } 1055 1056 if (!parentMenu->AddItem(newItem, index + 1)) { 1057 fprintf (stderr, "QPopupMenu::AddPersonItem: Unable to add menu " 1058 "item \"%s\" at index %ld.\n", sortKey.String(), index + 1); 1059 delete newItem; 1060 } 1061 } 1062 1063 1064 void 1065 QPopupMenu::EntryCreated(const entry_ref &ref, ino_t node) 1066 { 1067 BNode file; 1068 if (file.SetTo(&ref) < B_OK) 1069 return; 1070 1071 // Make sure the pop-up menu is ready for additions. Need a bunch of 1072 // groups at the top, a divider line, and miscellaneous people added below 1073 // the line. 1074 1075 int32 items = CountItems(); 1076 if (!items) 1077 AddSeparatorItem(); 1078 1079 // Does the file have a group attribute? OK to have none. 1080 BString groups; 1081 const char *kNoGroup = "NoGroup!"; 1082 file.ReadAttrString("META:group", &groups); 1083 if (groups.Length() <= 0) 1084 groups = kNoGroup; 1085 1086 // Add the e-mail address to the all people group. Then add it to all the 1087 // group menus that it exists in (based on the comma separated list of 1088 // groups from the People file), optionally making the group menu if it 1089 // doesn't exist. If it's in the special NoGroup! list, then add it below 1090 // the groups. 1091 1092 bool allPeopleGroupDone = false; 1093 BMenu *groupMenu; 1094 do { 1095 BString group; 1096 1097 if (!allPeopleGroupDone) { 1098 // Create the default group for all people, if it doesn't exist yet. 1099 group = "All People"; 1100 allPeopleGroupDone = true; 1101 } else { 1102 // Break out the next group from the comma separated string. 1103 int32 comma; 1104 if ((comma = groups.FindFirst(',')) > 0) { 1105 groups.MoveInto(group, 0, comma); 1106 groups.Remove(0, 1); 1107 } else 1108 group.Adopt(groups); 1109 } 1110 1111 // trim white spaces 1112 int32 i = 0; 1113 for (i = 0; isspace(group.ByteAt(i)); i++) {} 1114 if (i) 1115 group.Remove(0, i); 1116 for (i = group.Length() - 1; isspace(group.ByteAt(i)); i--) {} 1117 group.Truncate(i + 1); 1118 1119 groupMenu = NULL; 1120 BMenuItem *superItem = NULL; // Corresponding item for group menu. 1121 1122 if (group.Length() > 0 && group != kNoGroup) { 1123 BMenu *sub; 1124 1125 // Look for submenu with label == group name 1126 for (int32 i = 0; i < items; i++) { 1127 if ((sub = SubmenuAt(i)) != NULL) { 1128 superItem = sub->Superitem(); 1129 if (!strcmp(superItem->Label(), group.String())) { 1130 groupMenu = sub; 1131 i++; 1132 break; 1133 } 1134 } 1135 } 1136 1137 // If no submenu, create one 1138 if (!groupMenu) { 1139 // Find where it should go (alphabetical) 1140 int32 mindex = 0; 1141 for (; mindex < fGroups; mindex++) { 1142 if (strcmp(ItemAt(mindex)->Label(), group.String()) > 0) 1143 break; 1144 } 1145 1146 groupMenu = new BMenu(group.String()); 1147 groupMenu->SetFont(be_plain_font); 1148 AddItem(groupMenu, mindex); 1149 1150 superItem = groupMenu->Superitem(); 1151 superItem->SetMessage(new BMessage(B_SIMPLE_DATA)); 1152 if (fTargetHandler) 1153 superItem->SetTarget(fTargetHandler); 1154 1155 fGroups++; 1156 } 1157 } 1158 1159 BString name; 1160 file.ReadAttrString("META:name", &name); 1161 1162 BString email; 1163 file.ReadAttrString("META:email", &email); 1164 1165 if (email.Length() != 0 || name.Length() != 0) 1166 AddPersonItem(&ref, node, name, email, NULL, groupMenu, superItem); 1167 1168 // support for 3rd-party People apps 1169 for (int16 i = 2; i < 6; i++) { 1170 char attr[16]; 1171 sprintf(attr, "META:email%d", i); 1172 if (file.ReadAttrString(attr, &email) >= B_OK && email.Length() > 0) 1173 AddPersonItem(&ref, node, name, email, attr, groupMenu, superItem); 1174 } 1175 } while (groups.Length() > 0); 1176 } 1177 1178 1179 void 1180 QPopupMenu::EntryRemoved(ino_t /*node*/) 1181 { 1182 } 1183 1184