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 = "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 uint32 defaultChain) 138 : BBox(rect, "m_header", B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW, B_NO_BORDER), 139 fAccountMenu(NULL), 140 fEncodingMenu(NULL), 141 fChain(defaultChain), 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("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 BList chains; 322 if (GetOutboundMailChains(&chains) >= B_OK) { 323 bool marked = false; 324 for (int32 i = 0; i < chains.CountItems(); i++) { 325 BMailChain *chain = (BMailChain *)chains.ItemAt(i); 326 BString name = chain->Name(); 327 if ((msg = chain->MetaData()) != NULL) { 328 name << ": " << msg->FindString("real_name") 329 << " <" << msg->FindString("reply_to") << ">"; 330 } 331 BMenuItem *item = new BMenuItem(name.String(), 332 msg = new BMessage(kMsgFrom)); 333 334 msg->AddInt32("id", chain->ID()); 335 336 if (defaultChain == chain->ID()) { 337 item->SetMarked(true); 338 marked = true; 339 } 340 fAccountMenu->AddItem(item); 341 delete chain; 342 } 343 344 if (!marked) { 345 BMenuItem *item = fAccountMenu->ItemAt(0); 346 if (item != NULL) { 347 item->SetMarked(true); 348 fChain = item->Message()->FindInt32("id"); 349 } else { 350 fAccountMenu->AddItem(item = new BMenuItem("<none>",NULL)); 351 item->SetEnabled(false); 352 fChain = ~0UL; 353 } 354 // default chain is invalid, set to marked 355 // TODO: do this differently, no casting and knowledge 356 // of TMailApp here.... 357 if (TMailApp* app = dynamic_cast<TMailApp*>(be_app)) 358 app->SetDefaultChain(fChain); 359 } 360 } 361 r.Set(SEPARATOR_MARGIN, y - 2, 362 field->Frame().left - SEPARATOR_MARGIN, y + menuFieldHeight); 363 field = new BMenuField(r, "account", B_TRANSLATE("From:"), 364 fAccountMenu, true /* fixedSize */, 365 B_FOLLOW_TOP | B_FOLLOW_LEFT_RIGHT, 366 B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); 367 AddChild(field, encodingField); 368 field->SetDivider(x - 12 - SEPARATOR_MARGIN + kMenuFieldDividerOffset); 369 field->SetAlignment(B_ALIGN_RIGHT); 370 y += controlHeight; 371 } else { 372 // To: account 373 bool account = count_pop_accounts() > 0; 374 375 r.Set(SEPARATOR_MARGIN, y, 376 windowRect.Width() - SEPARATOR_MARGIN, y + menuFieldHeight); 377 if (account) 378 r.right -= SEPARATOR_MARGIN + ACCOUNT_FIELD_WIDTH; 379 fAccountTo = new TTextControl(r, B_TRANSLATE("To:"), NULL, fIncoming, 380 false, B_FOLLOW_LEFT_RIGHT); 381 fAccountTo->SetEnabled(false); 382 fAccountTo->SetDivider(x - 12 - SEPARATOR_MARGIN); 383 fAccountTo->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); 384 AddChild(fAccountTo); 385 386 if (account) { 387 r.left = r.right + 6; r.right = windowRect.Width() - SEPARATOR_MARGIN; 388 fAccount = new TTextControl(r, B_TRANSLATE("Account:"), NULL, 389 fIncoming, false, B_FOLLOW_RIGHT | B_FOLLOW_TOP); 390 fAccount->SetEnabled(false); 391 AddChild(fAccount); 392 } 393 y += controlHeight; 394 } 395 396 --y; 397 r.Set(SEPARATOR_MARGIN, y, 398 windowRect.Width() - SEPARATOR_MARGIN, y + menuFieldHeight); 399 y += controlHeight; 400 fSubject = new TTextControl(r, B_TRANSLATE("Subject:"), 401 new BMessage(SUBJECT_FIELD),fIncoming, false, B_FOLLOW_LEFT_RIGHT); 402 AddChild(fSubject); 403 (msg = new BMessage(FIELD_CHANGED))->AddInt32("bitmask", FIELD_SUBJECT); 404 fSubject->SetModificationMessage(msg); 405 fSubject->SetDivider(x - 12 - SEPARATOR_MARGIN); 406 fSubject->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); 407 if (fResending) 408 fSubject->SetEnabled(false); 409 410 --y; 411 412 if (!fIncoming) { 413 r.Set(x - 12, y, CC_FIELD_H + CC_FIELD_WIDTH, y + menuFieldHeight); 414 fCc = new TTextControl(r, "", new BMessage(CC_FIELD), fIncoming, false); 415 fCc->SetFilter(mail_to_filter); 416 fCc->SetChoiceList(&fEmailList); 417 fCc->SetAutoComplete(true); 418 AddChild(fCc); 419 (msg = new BMessage(FIELD_CHANGED))->AddInt32("bitmask", FIELD_CC); 420 fCc->SetModificationMessage(msg); 421 422 r.right = r.left - 5; 423 r.left = r.right - ceilf(be_plain_font->StringWidth( 424 B_TRANSLATE("Cc:")) + 25); 425 r.top -= 1; 426 fCcMenu = new QPopupMenu(B_TRANSLATE("Cc:")); 427 field = new BMenuField(r, "", "", fCcMenu, true, 428 B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW); 429 430 field->SetDivider(0.0); 431 field->SetEnabled(true); 432 AddChild(field); 433 434 r.Set(BCC_FIELD_H + be_plain_font->StringWidth(B_TRANSLATE("Bcc:")), y, 435 windowRect.Width() - SEPARATOR_MARGIN, y + menuFieldHeight); 436 y += controlHeight; 437 fBcc = new TTextControl(r, "", new BMessage(BCC_FIELD), 438 fIncoming, false, B_FOLLOW_LEFT_RIGHT); 439 fBcc->SetFilter(mail_to_filter); 440 fBcc->SetChoiceList(&fEmailList); 441 fBcc->SetAutoComplete(true); 442 AddChild(fBcc); 443 (msg = new BMessage(FIELD_CHANGED))->AddInt32("bitmask", FIELD_BCC); 444 fBcc->SetModificationMessage(msg); 445 446 r.right = r.left - 5; 447 r.left = r.right - ceilf(be_plain_font->StringWidth( 448 B_TRANSLATE("Bcc:")) + 25); 449 r.top -= 1; 450 fBccMenu = new QPopupMenu(B_TRANSLATE("Bcc:")); 451 field = new BMenuField(r, "", "", fBccMenu, true, 452 B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW); 453 field->SetDivider(0.0); 454 field->SetEnabled(true); 455 AddChild(field); 456 } else { 457 y -= SEPARATOR_MARGIN; 458 r.Set(SEPARATOR_MARGIN, y, x - 12 - 1, y + menuFieldHeight); 459 fDateLabel = new BStringView(r, "", kDateLabel); 460 fDateLabel->SetAlignment(B_ALIGN_RIGHT); 461 AddChild(fDateLabel); 462 fDateLabel->SetHighColor(0, 0, 0); 463 464 r.Set(r.right + 9, y, windowRect.Width() - SEPARATOR_MARGIN, 465 y + menuFieldHeight); 466 fDate = new BStringView(r, "", ""); 467 AddChild(fDate); 468 fDate->SetHighColor(0, 0, 0); 469 470 y += controlHeight + 5; 471 472 LoadMessage(mail); 473 } 474 ResizeTo(Bounds().Width(), y); 475 } 476 477 478 void 479 THeaderView::InitEmailCompletion() 480 { 481 // get boot volume 482 BVolume volume; 483 BVolumeRoster().GetBootVolume(&volume); 484 485 BQuery query; 486 query.SetVolume(&volume); 487 query.SetPredicate("META:email=**"); 488 // Due to R5 BFS bugs, you need two stars, META:email=** for the query. 489 // META:email="*" will just return one entry and stop, same with 490 // META:email=* and a few other variations. Grumble. 491 query.Fetch(); 492 entry_ref ref; 493 494 while (query.GetNextRef (&ref) == B_OK) { 495 BNode file; 496 if (file.SetTo(&ref) == B_OK) { 497 // Add the e-mail address as an auto-complete string. 498 BString email; 499 if (file.ReadAttrString("META:email", &email) >= B_OK) 500 fEmailList.AddChoice(email.String()); 501 502 // Also add the quoted full name as an auto-complete string. Can't 503 // do unquoted since auto-complete isn't that smart, so the user 504 // will have to type a quote mark if he wants to select someone by 505 // name. 506 BString fullName; 507 if (file.ReadAttrString("META:name", &fullName) >= B_OK) { 508 if (email.FindFirst('<') < 0) { 509 email.ReplaceAll('>', '_'); 510 email.Prepend("<"); 511 email.Append(">"); 512 } 513 fullName.ReplaceAll('\"', '_'); 514 fullName.Prepend("\""); 515 fullName << "\" " << email; 516 fEmailList.AddChoice(fullName.String()); 517 } 518 519 // support for 3rd-party People apps. Looks like a job for 520 // multiple keyword (so you can have several e-mail addresses in 521 // one attribute, perhaps comma separated) indices! Which aren't 522 // yet in BFS. 523 for (int16 i = 2; i < 6; i++) { 524 char attr[16]; 525 sprintf(attr, "META:email%d", i); 526 if (file.ReadAttrString(attr, &email) >= B_OK) 527 fEmailList.AddChoice(email.String()); 528 } 529 } 530 } 531 } 532 533 534 void 535 THeaderView::InitGroupCompletion() 536 { 537 // get boot volume 538 BVolume volume; 539 BVolumeRoster().GetBootVolume(&volume); 540 541 // Build a list of all unique groups and the addresses they expand to. 542 BQuery query; 543 query.SetVolume(&volume); 544 query.SetPredicate("META:group=**"); 545 query.Fetch(); 546 547 map<BString *, BString *, CompareBStrings> groupMap; 548 entry_ref ref; 549 BNode file; 550 while (query.GetNextRef(&ref) == B_OK) { 551 if (file.SetTo(&ref) != B_OK) 552 continue; 553 554 BString groups; 555 if (ReadAttrString(&file, "META:group", &groups) < B_OK || groups.Length() == 0) 556 continue; 557 558 BString address; 559 ReadAttrString(&file, "META:email", &address); 560 561 // avoid adding an empty address 562 if (address.Length() == 0) 563 continue; 564 565 char *group = groups.LockBuffer(groups.Length()); 566 char *next = strchr(group, ','); 567 568 for (;;) { 569 if (next) 570 *next = 0; 571 572 while (*group && *group == ' ') 573 group++; 574 575 BString *groupString = new BString(group); 576 BString *addressListString = NULL; 577 578 // nobody is in this group yet, start it off 579 if (groupMap[groupString] == NULL) { 580 addressListString = new BString(*groupString); 581 addressListString->Append(" "); 582 groupMap[groupString] = addressListString; 583 } else { 584 addressListString = groupMap[groupString]; 585 addressListString->Append(", "); 586 delete groupString; 587 } 588 589 // Append the user's address to the end of the string with the 590 // comma separated list of addresses. If not present, add the 591 // < and > brackets around the address. 592 593 if (address.FindFirst ('<') < 0) { 594 address.ReplaceAll ('>', '_'); 595 address.Prepend ("<"); 596 address.Append(">"); 597 } 598 addressListString->Append(address); 599 600 if (!next) 601 break; 602 603 group = next + 1; 604 next = strchr(group, ','); 605 } 606 } 607 608 map<BString *, BString *, CompareBStrings>::iterator iter; 609 for (iter = groupMap.begin(); iter != groupMap.end();) { 610 BString *group = iter->first; 611 BString *addr = iter->second; 612 fEmailList.AddChoice(addr->String()); 613 ++iter; 614 groupMap.erase(group); 615 delete group; 616 delete addr; 617 } 618 } 619 620 621 void 622 THeaderView::MessageReceived(BMessage *msg) 623 { 624 switch (msg->what) { 625 case B_SIMPLE_DATA: 626 { 627 BTextView *textView = dynamic_cast<BTextView *>(Window()->CurrentFocus()); 628 if (dynamic_cast<TTextControl *>(textView->Parent()) != NULL) 629 textView->Parent()->MessageReceived(msg); 630 else { 631 BMessage message(*msg); 632 message.what = REFS_RECEIVED; 633 Window()->PostMessage(&message, Window()); 634 } 635 break; 636 } 637 638 case kMsgFrom: 639 { 640 BMenuItem *item; 641 if (msg->FindPointer("source", (void **)&item) >= B_OK) 642 item->SetMarked(true); 643 644 uint32 chain; 645 if (msg->FindInt32("id",(int32 *)&chain) >= B_OK) 646 fChain = chain; 647 break; 648 } 649 650 case kMsgEncoding: 651 { 652 BMessage message(*msg); 653 int32 charSet; 654 655 if (msg->FindInt32("charset", &charSet) == B_OK) 656 fCharacterSetUserSees = charSet; 657 658 message.what = CHARSET_CHOICE_MADE; 659 message.AddInt32 ("charset", fCharacterSetUserSees); 660 Window()->PostMessage (&message, Window()); 661 break; 662 } 663 } 664 } 665 666 667 void 668 THeaderView::AttachedToWindow() 669 { 670 if (fToMenu) { 671 fToMenu->SetTargetForItems(fTo); 672 fToMenu->SetPredicate("META:email=**"); 673 } 674 if (fCcMenu) { 675 fCcMenu->SetTargetForItems(fCc); 676 fCcMenu->SetPredicate("META:email=**"); 677 } 678 if (fBccMenu) { 679 fBccMenu->SetTargetForItems(fBcc); 680 fBccMenu->SetPredicate("META:email=**"); 681 } 682 if (fTo) 683 fTo->SetTarget(Looper()); 684 if (fSubject) 685 fSubject->SetTarget(Looper()); 686 if (fCc) 687 fCc->SetTarget(Looper()); 688 if (fBcc) 689 fBcc->SetTarget(Looper()); 690 if (fAccount) 691 fAccount->SetTarget(Looper()); 692 if (fAccountMenu) 693 fAccountMenu->SetTargetForItems(this); 694 if (fEncodingMenu) 695 fEncodingMenu->SetTargetForItems(this); 696 697 BBox::AttachedToWindow(); 698 } 699 700 701 status_t 702 THeaderView::LoadMessage(BEmailMessage *mail) 703 { 704 // Set the date on this message 705 const char *dateField = mail->Date(); 706 char string[256]; 707 sprintf(string, "%s", dateField != NULL ? dateField : "Unknown"); 708 fDate->SetText(string); 709 710 // Set contents of header fields 711 if (fIncoming && !fResending) { 712 if (fBcc != NULL) 713 fBcc->SetEnabled(false); 714 715 if (fCc != NULL) 716 fCc->SetEnabled(false); 717 718 if (fAccount != NULL) 719 fAccount->SetEnabled(false); 720 721 if (fAccountTo != NULL) 722 fAccountTo->SetEnabled(false); 723 724 fSubject->SetEnabled(false); 725 fTo->SetEnabled(false); 726 } 727 728 // Set Subject: & From: fields 729 fSubject->SetText(mail->Subject()); 730 fTo->SetText(mail->From()); 731 732 // Set Account/To Field 733 if (fAccountTo != NULL) 734 fAccountTo->SetText(mail->To()); 735 736 if (fAccount != NULL && mail->GetAccountName(string,sizeof(string)) == B_OK) 737 fAccount->SetText(string); 738 739 return B_OK; 740 } 741 742 743 // #pragma mark - TTextControl 744 745 746 TTextControl::TTextControl(BRect rect, const char *label, BMessage *msg, 747 bool incoming, bool resending, int32 resizingMode) 748 : BComboBox(rect, "happy", label, msg, resizingMode), 749 fRefDropMenu(NULL) 750 //:BTextControl(rect, "happy", label, "", msg, resizingMode) 751 { 752 strcpy(fLabel, label); 753 fCommand = msg != NULL ? msg->what : 0UL; 754 fIncoming = incoming; 755 fResending = resending; 756 } 757 758 759 void 760 TTextControl::AttachedToWindow() 761 { 762 SetHighColor(0, 0, 0); 763 // BTextControl::AttachedToWindow(); 764 BComboBox::AttachedToWindow(); 765 766 SetDivider(Divider() + kTextControlDividerOffset); 767 } 768 769 770 void 771 TTextControl::MessageReceived(BMessage *msg) 772 { 773 switch (msg->what) { 774 case B_SIMPLE_DATA: { 775 if (fIncoming && !fResending) 776 return; 777 778 int32 buttons = -1; 779 BPoint point; 780 if (msg->FindInt32("buttons", &buttons) != B_OK) 781 buttons = B_PRIMARY_MOUSE_BUTTON; 782 783 if (buttons != B_PRIMARY_MOUSE_BUTTON 784 && msg->FindPoint("_drop_point_", &point) != B_OK) 785 return; 786 787 BMessage message(REFS_RECEIVED); 788 bool enclosure = false; 789 BString addressList; 790 // Batch up the addresses to be added, since we can only 791 // insert a few times before deadlocking since inserting 792 // sends a notification message to the window BLooper, 793 // which is busy doing this insert. BeOS message queues 794 // are annoyingly limited in their design. 795 796 entry_ref ref; 797 for (int32 index = 0;msg->FindRef("refs", index, &ref) == B_OK; index++) { 798 BFile file(&ref, B_READ_ONLY); 799 if (file.InitCheck() == B_NO_ERROR) { 800 BNodeInfo info(&file); 801 char type[B_FILE_NAME_LENGTH]; 802 info.GetType(type); 803 804 if (fCommand != SUBJECT_FIELD 805 && !strcmp(type,"application/x-person")) { 806 // add person's E-mail address to the To: field 807 808 BString attr = ""; 809 if (buttons == B_PRIMARY_MOUSE_BUTTON) { 810 if (msg->FindString("attr", &attr) < B_OK) 811 attr = "META:email"; // If not META:email3 etc. 812 } else { 813 BNode node(&ref); 814 node.RewindAttrs(); 815 816 char buffer[B_ATTR_NAME_LENGTH]; 817 818 delete fRefDropMenu; 819 fRefDropMenu = new BPopUpMenu("RecipientMenu"); 820 821 while (node.GetNextAttrName(buffer) == B_OK) { 822 if (strstr(buffer, "email") <= 0) 823 continue; 824 825 attr = buffer; 826 827 BString address; 828 ReadAttrString(&node, buffer, &address); 829 if (address.Length() <= 0) 830 continue; 831 832 BMessage *itemMsg = new BMessage(kMsgAddressChosen); 833 itemMsg->AddString("address", address.String()); 834 itemMsg->AddRef("ref", &ref); 835 836 BMenuItem *item = new BMenuItem(address.String(), 837 itemMsg); 838 fRefDropMenu->AddItem(item); 839 } 840 841 if (fRefDropMenu->CountItems() > 1) { 842 fRefDropMenu->SetTargetForItems(this); 843 fRefDropMenu->Go(point, true, true, true); 844 return; 845 } else { 846 delete fRefDropMenu; 847 fRefDropMenu = NULL; 848 } 849 } 850 851 BString email; 852 ReadAttrString(&file,attr.String(),&email); 853 854 // we got something... 855 if (email.Length() > 0) { 856 // see if we can get a username as well 857 BString name; 858 ReadAttrString(&file, "META:name", &name); 859 860 BString address; 861 if (name.Length() == 0) { 862 // if we have no Name, just use the email address 863 address = email; 864 } else { 865 // otherwise, pretty-format it 866 address << "\"" << name << "\" <" << email << ">"; 867 } 868 869 if (addressList.Length() > 0) 870 addressList << ", "; 871 addressList << address; 872 } 873 } else { 874 enclosure = true; 875 message.AddRef("refs", &ref); 876 } 877 } 878 } 879 880 if (addressList.Length() > 0) { 881 BTextView *textView = TextView(); 882 int end = textView->TextLength(); 883 if (end != 0) { 884 textView->Select(end, end); 885 textView->Insert(", "); 886 } 887 textView->Insert(addressList.String()); 888 } 889 890 if (enclosure) 891 Window()->PostMessage(&message, Window()); 892 break; 893 } 894 895 case M_SELECT: 896 { 897 BTextView *textView = (BTextView *)ChildAt(0); 898 if (textView != NULL) 899 textView->Select(0, textView->TextLength()); 900 break; 901 } 902 903 case kMsgAddressChosen: { 904 BString display; 905 BString address; 906 entry_ref ref; 907 908 if (msg->FindString("address", &address) != B_OK 909 || msg->FindRef("ref", &ref) != B_OK) 910 return; 911 912 if (address.Length() > 0) { 913 BString name; 914 BNode node(&ref); 915 916 display = address; 917 918 ReadAttrString(&node, "META:name", &name); 919 if (name.Length() > 0) { 920 display = ""; 921 display << "\"" << name << "\" <" << address << ">"; 922 } 923 924 BTextView *textView = TextView(); 925 int end = textView->TextLength(); 926 if (end != 0) { 927 textView->Select(end, end); 928 textView->Insert(", "); 929 } 930 textView->Insert(display.String()); 931 } 932 break; 933 } 934 935 default: 936 // BTextControl::MessageReceived(msg); 937 BComboBox::MessageReceived(msg); 938 } 939 } 940 941 942 bool 943 TTextControl::HasFocus() 944 { 945 return TextView()->IsFocus(); 946 } 947 948 949 // #pragma mark - QPopupMenu 950 951 952 QPopupMenu::QPopupMenu(const char *title) 953 : QueryMenu(title, true), 954 fGroups(0) 955 { 956 } 957 958 959 void 960 QPopupMenu::AddPersonItem(const entry_ref *ref, ino_t node, BString &name, 961 BString &email, const char *attr, BMenu *groupMenu, BMenuItem *superItem) 962 { 963 BString label; 964 BString sortKey; 965 // For alphabetical order sorting, usually last name. 966 967 // if we have no Name, just use the email address 968 if (name.Length() == 0) { 969 label = email; 970 sortKey = email; 971 } else { 972 // otherwise, pretty-format it 973 label << name << " (" << email << ")"; 974 975 // Extract the last name (last word in the name), 976 // removing trailing and leading spaces. 977 const char *nameStart = name.String(); 978 const char *string = nameStart + strlen(nameStart) - 1; 979 const char *wordEnd; 980 981 while (string >= nameStart && isspace(*string)) 982 string--; 983 wordEnd = string + 1; // Points to just after last word. 984 while (string >= nameStart && !isspace(*string)) 985 string--; 986 string++; // Point to first letter in the word. 987 if (wordEnd > string) 988 sortKey.SetTo(string, wordEnd - string); 989 else // Blank name, pretend that the last name is after it. 990 string = nameStart + strlen(nameStart); 991 992 // Append the first names to the end, so that people with the same last 993 // name get sorted by first name. Note no space between the end of the 994 // last name and the start of the first names, but that shouldn't 995 // matter for sorting. 996 sortKey.Append(nameStart, string - nameStart); 997 } 998 999 // The target (a TTextControl) will examine all the People files specified 1000 // and add the emails and names to the string it is displaying (same code 1001 // is used for drag and drop of People files). 1002 BMessage *msg = new BMessage(B_SIMPLE_DATA); 1003 msg->AddRef("refs", ref); 1004 msg->AddInt64("node", node); 1005 if (attr) // For nonstandard e-mail attributes, like META:email3 1006 msg->AddString("attr", attr); 1007 msg->AddString("sortkey", sortKey); 1008 1009 BMenuItem *newItem = new BMenuItem(label.String(), msg); 1010 if (fTargetHandler) 1011 newItem->SetTarget(fTargetHandler); 1012 1013 // If no group, just add it to ourself; else add it to group menu 1014 BMenu *parentMenu = groupMenu ? groupMenu : this; 1015 if (groupMenu) { 1016 // Add ref to group super item. 1017 BMessage *superMsg = superItem->Message(); 1018 superMsg->AddRef("refs", ref); 1019 } 1020 1021 // Add it to the appropriate menu. Use alphabetical order by sortKey to 1022 // insert it in the right spot (a dumb linear search so this will be slow). 1023 // Start searching from the end of the menu, since the main menu includes 1024 // all the groups at the top and we don't want to mix it in with them. 1025 // Thus the search starts at the bottom and ends when we hit a separator 1026 // line or the top of the menu. 1027 1028 int32 index = parentMenu->CountItems(); 1029 while (index-- > 0) { 1030 BMenuItem *item = parentMenu->ItemAt(index); 1031 if (item == NULL || dynamic_cast<BSeparatorItem *>(item) != NULL) 1032 break; 1033 1034 BMessage *message = item->Message(); 1035 BString key; 1036 1037 // Stop when testKey < sortKey. 1038 if (message != NULL 1039 && message->FindString("sortkey", &key) == B_OK 1040 && ICompare(key, sortKey) < 0) 1041 break; 1042 } 1043 1044 if (!parentMenu->AddItem(newItem, index + 1)) { 1045 fprintf (stderr, "QPopupMenu::AddPersonItem: Unable to add menu " 1046 "item \"%s\" at index %ld.\n", sortKey.String(), index + 1); 1047 delete newItem; 1048 } 1049 } 1050 1051 1052 void 1053 QPopupMenu::EntryCreated(const entry_ref &ref, ino_t node) 1054 { 1055 BNode file; 1056 if (file.SetTo(&ref) < B_OK) 1057 return; 1058 1059 // Make sure the pop-up menu is ready for additions. Need a bunch of 1060 // groups at the top, a divider line, and miscellaneous people added below 1061 // the line. 1062 1063 int32 items = CountItems(); 1064 if (!items) 1065 AddSeparatorItem(); 1066 1067 // Does the file have a group attribute? OK to have none. 1068 BString groups; 1069 const char *kNoGroup = "NoGroup!"; 1070 ReadAttrString(&file, "META:group", &groups); 1071 if (groups.Length() <= 0) 1072 groups = kNoGroup; 1073 1074 // Add the e-mail address to the all people group. Then add it to all the 1075 // group menus that it exists in (based on the comma separated list of 1076 // groups from the People file), optionally making the group menu if it 1077 // doesn't exist. If it's in the special NoGroup! list, then add it below 1078 // the groups. 1079 1080 bool allPeopleGroupDone = false; 1081 BMenu *groupMenu; 1082 do { 1083 BString group; 1084 1085 if (!allPeopleGroupDone) { 1086 // Create the default group for all people, if it doesn't exist yet. 1087 group = "All People"; 1088 allPeopleGroupDone = true; 1089 } else { 1090 // Break out the next group from the comma separated string. 1091 int32 comma; 1092 if ((comma = groups.FindFirst(',')) > 0) { 1093 groups.MoveInto(group, 0, comma); 1094 groups.Remove(0, 1); 1095 } else 1096 group.Adopt(groups); 1097 } 1098 1099 // trim white spaces 1100 int32 i = 0; 1101 for (i = 0; isspace(group.ByteAt(i)); i++) {} 1102 if (i) 1103 group.Remove(0, i); 1104 for (i = group.Length() - 1; isspace(group.ByteAt(i)); i--) {} 1105 group.Truncate(i + 1); 1106 1107 groupMenu = NULL; 1108 BMenuItem *superItem = NULL; // Corresponding item for group menu. 1109 1110 if (group.Length() > 0 && group != kNoGroup) { 1111 BMenu *sub; 1112 1113 // Look for submenu with label == group name 1114 for (int32 i = 0; i < items; i++) { 1115 if ((sub = SubmenuAt(i)) != NULL) { 1116 superItem = sub->Superitem(); 1117 if (!strcmp(superItem->Label(), group.String())) { 1118 groupMenu = sub; 1119 i++; 1120 break; 1121 } 1122 } 1123 } 1124 1125 // If no submenu, create one 1126 if (!groupMenu) { 1127 // Find where it should go (alphabetical) 1128 int32 mindex = 0; 1129 for (; mindex < fGroups; mindex++) { 1130 if (strcmp(ItemAt(mindex)->Label(), group.String()) > 0) 1131 break; 1132 } 1133 1134 groupMenu = new BMenu(group.String()); 1135 groupMenu->SetFont(be_plain_font); 1136 AddItem(groupMenu, mindex); 1137 1138 superItem = groupMenu->Superitem(); 1139 superItem->SetMessage(new BMessage(B_SIMPLE_DATA)); 1140 if (fTargetHandler) 1141 superItem->SetTarget(fTargetHandler); 1142 1143 fGroups++; 1144 } 1145 } 1146 1147 BString name; 1148 ReadAttrString(&file, "META:name", &name); 1149 1150 BString email; 1151 ReadAttrString(&file, "META:email", &email); 1152 1153 if (email.Length() != 0 || name.Length() != 0) 1154 AddPersonItem(&ref, node, name, email, NULL, groupMenu, superItem); 1155 1156 // support for 3rd-party People apps 1157 for (int16 i = 2; i < 6; i++) { 1158 char attr[16]; 1159 sprintf(attr, "META:email%d", i); 1160 if (ReadAttrString(&file, attr, &email) >= B_OK && email.Length() > 0) 1161 AddPersonItem(&ref, node, name, email, attr, groupMenu, superItem); 1162 } 1163 } while (groups.Length() > 0); 1164 } 1165 1166 1167 void 1168 QPopupMenu::EntryRemoved(ino_t /*node*/) 1169 { 1170 } 1171 1172