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