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