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 36 #include "MailWindow.h" 37 38 #include <fcntl.h> 39 #include <stdio.h> 40 #include <stdlib.h> 41 #include <strings.h> 42 #include <sys/stat.h> 43 #include <sys/utsname.h> 44 #include <syslog.h> 45 #include <unistd.h> 46 47 #include <AppFileInfo.h> 48 #include <Autolock.h> 49 #include <Bitmap.h> 50 #include <Button.h> 51 #include <CharacterSet.h> 52 #include <CharacterSetRoster.h> 53 #include <Clipboard.h> 54 #include <ControlLook.h> 55 #include <Debug.h> 56 #include <E-mail.h> 57 #include <File.h> 58 #include <IconUtils.h> 59 #include <LayoutBuilder.h> 60 #include <Locale.h> 61 #include <Node.h> 62 #include <PathMonitor.h> 63 #include <PrintJob.h> 64 #include <Query.h> 65 #include <Resources.h> 66 #include <Roster.h> 67 #include <Screen.h> 68 #include <String.h> 69 #include <StringList.h> 70 #include <StringView.h> 71 #include <TextView.h> 72 #include <UTF8.h> 73 #include <VolumeRoster.h> 74 75 #include <fs_index.h> 76 #include <fs_info.h> 77 78 #include <MailMessage.h> 79 #include <MailSettings.h> 80 #include <MailDaemon.h> 81 #include <mail_util.h> 82 83 #include <CharacterSetRoster.h> 84 85 #include "AttributeUtilities.h" 86 #include "Content.h" 87 #include "Enclosures.h" 88 #include "FieldMsg.h" 89 #include "FindWindow.h" 90 #include "Header.h" 91 #include "Messages.h" 92 #include "MailApp.h" 93 #include "MailPopUpMenu.h" 94 #include "MailSupport.h" 95 #include "Prefs.h" 96 #include "QueryMenu.h" 97 #include "Signature.h" 98 #include "Settings.h" 99 #include "Status.h" 100 #include "String.h" 101 #include "Utilities.h" 102 103 104 #define B_TRANSLATION_CONTEXT "Mail" 105 106 107 using namespace BPrivate; 108 109 110 const char* kUndoStrings[] = { 111 "Undo", 112 "Undo typing", 113 "Undo cut", 114 "Undo paste", 115 "Undo clear", 116 "Undo drop" 117 }; 118 119 const char* kRedoStrings[] = { 120 "Redo", 121 "Redo typing", 122 "Redo cut", 123 "Redo paste", 124 "Redo clear", 125 "Redo drop" 126 }; 127 128 129 // Text for both the main menu and the pop-up menu. 130 static const char* kSpamMenuItemTextArray[] = { 131 "Mark as spam and move to trash", // M_TRAIN_SPAM_AND_DELETE 132 "Mark as spam", // M_TRAIN_SPAM 133 "Unmark this message", // M_UNTRAIN 134 "Mark as genuine" // M_TRAIN_GENUINE 135 }; 136 137 static const uint32 kMsgQuitAndKeepAllStatus = 'Casm'; 138 139 static const char* kQueriesDirectory = "mail/queries"; 140 static const char* kAttrQueryInitialMode = "_trk/qryinitmode"; 141 // taken from src/kits/tracker/Attributes.h 142 static const char* kAttrQueryInitialString = "_trk/qryinitstr"; 143 static const char* kAttrQueryInitialNumAttrs = "_trk/qryinitnumattrs"; 144 static const char* kAttrQueryInitialAttrs = "_trk/qryinitattrs"; 145 static const uint32 kAttributeItemMain = 'Fatr'; 146 // taken from src/kits/tracker/FindPanel.h 147 static const uint32 kByNameItem = 'Fbyn'; 148 // taken from src/kits/tracker/FindPanel.h 149 static const uint32 kByAttributeItem = 'Fbya'; 150 // taken from src/kits/tracker/FindPanel.h 151 static const uint32 kByForumlaItem = 'Fbyq'; 152 // taken from src/kits/tracker/FindPanel.h 153 static const int kCopyBufferSize = 64 * 1024; // 64 KB 154 155 static const char* kSameRecipientItem = B_TRANSLATE("Same recipient"); 156 static const char* kSameSenderItem = B_TRANSLATE("Same sender"); 157 static const char* kSameSubjectItem = B_TRANSLATE("Same subject"); 158 159 160 // static bitmap cache 161 BObjectList<TMailWindow::BitmapItem> TMailWindow::sBitmapCache; 162 BLocker TMailWindow::sBitmapCacheLock; 163 164 // static list for tracking of Windows 165 BList TMailWindow::sWindowList; 166 BLocker TMailWindow::sWindowListLock; 167 168 169 class HorizontalLine : public BView { 170 public: 171 HorizontalLine(BRect rect) 172 : 173 BView (rect, NULL, B_FOLLOW_ALL, B_WILL_DRAW) 174 { 175 } 176 177 virtual void Draw(BRect rect) 178 { 179 FillRect(rect, B_SOLID_HIGH); 180 } 181 }; 182 183 184 // #pragma mark - 185 186 187 TMailWindow::TMailWindow(BRect rect, const char* title, TMailApp* app, 188 const entry_ref* ref, const char* to, const BFont* font, bool resending, 189 BMessenger* messenger) 190 : 191 BWindow(rect, title, B_DOCUMENT_WINDOW, B_AUTO_UPDATE_SIZE_LIMITS), 192 193 fApp(app), 194 fMail(NULL), 195 fRef(NULL), 196 fFieldState(0), 197 fPanel(NULL), 198 fSaveAddrMenu(NULL), 199 fLeaveStatusMenu(NULL), 200 fEncodingMenu(NULL), 201 fZoom(rect), 202 fEnclosuresView(NULL), 203 fPrevTrackerPositionSaved(false), 204 fNextTrackerPositionSaved(false), 205 fSigAdded(false), 206 fReplying(false), 207 fResending(resending), 208 fSent(false), 209 fDraft(false), 210 fChanged(false), 211 fOriginatingWindow(NULL), 212 213 fDownloading(false) 214 { 215 fKeepStatusOnQuit = false; 216 217 if (messenger != NULL) 218 fTrackerMessenger = *messenger; 219 220 BFile file(ref, B_READ_ONLY); 221 if (ref) { 222 fRef = new entry_ref(*ref); 223 fIncoming = true; 224 } else 225 fIncoming = false; 226 227 fAutoMarkRead = fApp->AutoMarkRead(); 228 fMenuBar = new BMenuBar("menuBar"); 229 230 // File Menu 231 232 BMenu* menu = new BMenu(B_TRANSLATE("File")); 233 234 BMessage* msg = new BMessage(M_NEW); 235 msg->AddInt32("type", M_NEW); 236 BMenuItem* item = new BMenuItem(B_TRANSLATE("New mail message"), msg, 'N'); 237 menu->AddItem(item); 238 item->SetTarget(be_app); 239 240 // Cheap hack - only show the drafts menu when composing messages. Insert 241 // a "true || " in the following IF statement if you want the old BeMail 242 // behaviour. The difference is that without live draft menu updating you 243 // can open around 100 e-mails (the BeOS maximum number of open files) 244 // rather than merely around 20, since each open draft-monitoring query 245 // sucks up one file handle per mounted BFS disk volume. Plus mail file 246 // opening speed is noticably improved! ToDo: change this to populate the 247 // Draft menu with the file names on demand - when the user clicks on it; 248 // don't need a live query since the menu isn't staying up for more than a 249 // few seconds. 250 251 if (!fIncoming) { 252 QueryMenu* queryMenu = new QueryMenu(B_TRANSLATE("Open draft"), false); 253 queryMenu->SetTargetForItems(be_app); 254 255 queryMenu->SetPredicate("MAIL:draft==1"); 256 menu->AddItem(queryMenu); 257 } 258 259 if (!fIncoming || resending) { 260 menu->AddItem(fSendLater = new BMenuItem(B_TRANSLATE("Save as draft"), 261 new BMessage(M_SAVE_AS_DRAFT), 'S')); 262 } 263 264 if (!resending && fIncoming) { 265 menu->AddSeparatorItem(); 266 267 BMenu* subMenu = new BMenu(B_TRANSLATE("Close and ")); 268 269 read_flags flag; 270 read_read_attr(file, flag); 271 272 if (flag == B_UNREAD) { 273 subMenu->AddItem(new BMenuItem( 274 B_TRANSLATE_COMMENT("Leave as 'New'", 275 "Do not translate New - this is non-localizable e-mail status"), 276 new BMessage(kMsgQuitAndKeepAllStatus), 'W', B_SHIFT_KEY)); 277 } else { 278 BString status; 279 file.ReadAttrString(B_MAIL_ATTR_STATUS, &status); 280 281 BString label; 282 if (status.Length() > 0) 283 label.SetToFormat(B_TRANSLATE("Leave as '%s'"), 284 status.String()); 285 else 286 label = B_TRANSLATE("Leave same"); 287 288 subMenu->AddItem(new BMenuItem(label.String(), 289 new BMessage(B_QUIT_REQUESTED), 'W')); 290 AddShortcut('W', B_COMMAND_KEY | B_SHIFT_KEY, 291 new BMessage(kMsgQuitAndKeepAllStatus)); 292 } 293 294 subMenu->AddItem(new BMenuItem(B_TRANSLATE("Move to trash"), 295 new BMessage(M_DELETE), 'T', B_CONTROL_KEY)); 296 AddShortcut('T', B_SHIFT_KEY | B_COMMAND_KEY, 297 new BMessage(M_DELETE_NEXT)); 298 299 subMenu->AddSeparatorItem(); 300 301 subMenu->AddItem(new BMenuItem(B_TRANSLATE("Set to Saved"), 302 new BMessage(M_CLOSE_SAVED), 'W', B_CONTROL_KEY)); 303 304 if (add_query_menu_items(subMenu, INDEX_STATUS, M_STATUS, 305 B_TRANSLATE("Set to %s")) > 0) 306 subMenu->AddSeparatorItem(); 307 308 subMenu->AddItem(new BMenuItem(B_TRANSLATE("Set to" B_UTF8_ELLIPSIS), 309 new BMessage(M_CLOSE_CUSTOM))); 310 311 #if 0 312 subMenu->AddItem(new BMenuItem(new TMenu( 313 B_TRANSLATE("Set to" B_UTF8_ELLIPSIS), INDEX_STATUS, M_STATUS, 314 false, false), 315 new BMessage(M_CLOSE_CUSTOM))); 316 #endif 317 menu->AddItem(subMenu); 318 319 fLeaveStatusMenu = subMenu; 320 } else { 321 menu->AddSeparatorItem(); 322 menu->AddItem(new BMenuItem(B_TRANSLATE("Close"), 323 new BMessage(B_CLOSE_REQUESTED), 'W')); 324 } 325 326 menu->AddSeparatorItem(); 327 menu->AddItem(fPrint = new BMenuItem( 328 B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS), 329 new BMessage(M_PRINT_SETUP))); 330 menu->AddItem(fPrint = new BMenuItem( 331 B_TRANSLATE("Print" B_UTF8_ELLIPSIS), 332 new BMessage(M_PRINT), 'P')); 333 fMenuBar->AddItem(menu); 334 335 menu->AddSeparatorItem(); 336 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Quit"), 337 new BMessage(B_QUIT_REQUESTED), 'Q')); 338 item->SetTarget(be_app); 339 340 // Edit Menu 341 342 menu = new BMenu(B_TRANSLATE("Edit")); 343 menu->AddItem(fUndo = new BMenuItem(B_TRANSLATE("Undo"), 344 new BMessage(B_UNDO), 'Z', 0)); 345 fUndo->SetTarget(NULL, this); 346 menu->AddItem(fRedo = new BMenuItem(B_TRANSLATE("Redo"), 347 new BMessage(M_REDO), 'Z', B_SHIFT_KEY)); 348 fRedo->SetTarget(NULL, this); 349 menu->AddSeparatorItem(); 350 menu->AddItem(fCut = new BMenuItem(B_TRANSLATE("Cut"), 351 new BMessage(B_CUT), 'X')); 352 fCut->SetTarget(NULL, this); 353 menu->AddItem(fCopy = new BMenuItem(B_TRANSLATE("Copy"), 354 new BMessage(B_COPY), 'C')); 355 fCopy->SetTarget(NULL, this); 356 menu->AddItem(fPaste = new BMenuItem(B_TRANSLATE("Paste"), 357 new BMessage(B_PASTE), 358 'V')); 359 fPaste->SetTarget(NULL, this); 360 menu->AddSeparatorItem(); 361 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Select all"), 362 new BMessage(M_SELECT), 'A')); 363 menu->AddSeparatorItem(); 364 item->SetTarget(NULL, this); 365 menu->AddItem(new BMenuItem(B_TRANSLATE("Find" B_UTF8_ELLIPSIS), 366 new BMessage(M_FIND), 'F')); 367 menu->AddItem(new BMenuItem(B_TRANSLATE("Find again"), 368 new BMessage(M_FIND_AGAIN), 'G')); 369 if (!fIncoming) { 370 menu->AddSeparatorItem(); 371 fQuote = new BMenuItem(B_TRANSLATE("Increase quote level"), 372 new BMessage(M_ADD_QUOTE_LEVEL), '+'); 373 menu->AddItem(fQuote); 374 fRemoveQuote = new BMenuItem(B_TRANSLATE("Decrease quote level"), 375 new BMessage(M_SUB_QUOTE_LEVEL), '-'); 376 menu->AddItem(fRemoveQuote); 377 378 menu->AddSeparatorItem(); 379 fSpelling = new BMenuItem(B_TRANSLATE("Check spelling"), 380 new BMessage(M_CHECK_SPELLING), ';'); 381 menu->AddItem(fSpelling); 382 if (fApp->StartWithSpellCheckOn()) 383 PostMessage(M_CHECK_SPELLING); 384 } 385 menu->AddSeparatorItem(); 386 menu->AddItem(item = new BMenuItem( 387 B_TRANSLATE("Settings" B_UTF8_ELLIPSIS), 388 new BMessage(M_PREFS), ',')); 389 item->SetTarget(be_app); 390 fMenuBar->AddItem(menu); 391 menu->AddItem(item = new BMenuItem( 392 B_TRANSLATE("Accounts" B_UTF8_ELLIPSIS), 393 new BMessage(M_ACCOUNTS))); 394 item->SetTarget(be_app); 395 396 // View Menu 397 398 if (!resending && fIncoming) { 399 menu = new BMenu(B_TRANSLATE("View")); 400 menu->AddItem(fHeader = new BMenuItem(B_TRANSLATE("Show header"), 401 new BMessage(M_HEADER), 'H')); 402 menu->AddItem(fRaw = new BMenuItem(B_TRANSLATE("Show raw message"), 403 new BMessage(M_RAW))); 404 fMenuBar->AddItem(menu); 405 } 406 407 // Message Menu 408 409 menu = new BMenu(B_TRANSLATE("Message")); 410 411 if (!resending && fIncoming) { 412 menu->AddItem(new BMenuItem(B_TRANSLATE("Reply"), 413 new BMessage(M_REPLY),'R')); 414 menu->AddItem(new BMenuItem(B_TRANSLATE("Reply to sender"), 415 new BMessage(M_REPLY_TO_SENDER),'R',B_OPTION_KEY)); 416 menu->AddItem(new BMenuItem(B_TRANSLATE("Reply to all"), 417 new BMessage(M_REPLY_ALL), 'R', B_SHIFT_KEY)); 418 419 menu->AddSeparatorItem(); 420 421 menu->AddItem(new BMenuItem(B_TRANSLATE("Forward"), 422 new BMessage(M_FORWARD), 'J')); 423 menu->AddItem(new BMenuItem(B_TRANSLATE("Forward without attachments"), 424 new BMessage(M_FORWARD_WITHOUT_ATTACHMENTS))); 425 menu->AddItem(new BMenuItem(B_TRANSLATE("Resend"), 426 new BMessage(M_RESEND))); 427 menu->AddItem(new BMenuItem(B_TRANSLATE("Copy to new"), 428 new BMessage(M_COPY_TO_NEW), 'D')); 429 430 menu->AddSeparatorItem(); 431 fDeleteNext = new BMenuItem(B_TRANSLATE("Move to trash"), 432 new BMessage(M_DELETE_NEXT), 'T'); 433 menu->AddItem(fDeleteNext); 434 menu->AddSeparatorItem(); 435 436 fPrevMsg = new BMenuItem(B_TRANSLATE("Previous message"), 437 new BMessage(M_PREVMSG), B_UP_ARROW); 438 menu->AddItem(fPrevMsg); 439 fNextMsg = new BMenuItem(B_TRANSLATE("Next message"), 440 new BMessage(M_NEXTMSG), B_DOWN_ARROW); 441 menu->AddItem(fNextMsg); 442 } else { 443 menu->AddItem(fSendNow = new BMenuItem(B_TRANSLATE("Send message"), 444 new BMessage(M_SEND_NOW), 'M')); 445 446 if (!fIncoming) { 447 menu->AddSeparatorItem(); 448 fSignature = new TMenu(B_TRANSLATE("Add signature"), 449 INDEX_SIGNATURE, M_SIGNATURE); 450 menu->AddItem(new BMenuItem(fSignature)); 451 menu->AddItem(item = new BMenuItem( 452 B_TRANSLATE("Edit signatures" B_UTF8_ELLIPSIS), 453 new BMessage(M_EDIT_SIGNATURE))); 454 item->SetTarget(be_app); 455 menu->AddSeparatorItem(); 456 menu->AddItem(fAdd = new BMenuItem( 457 B_TRANSLATE("Add attachment" B_UTF8_ELLIPSIS), 458 new BMessage(M_ADD), 'E')); 459 menu->AddItem(fRemove = new BMenuItem( 460 B_TRANSLATE("Remove attachment"), 461 new BMessage(M_REMOVE), 'T')); 462 } 463 } 464 if (fIncoming) { 465 menu->AddSeparatorItem(); 466 fSaveAddrMenu = new BMenu(B_TRANSLATE("Save address")); 467 menu->AddItem(fSaveAddrMenu); 468 } 469 470 // Encoding menu 471 472 fEncodingMenu = new BMenu(B_TRANSLATE("Encoding")); 473 474 BMenuItem* automaticItem = NULL; 475 if (!resending && fIncoming) { 476 // Reading a message, display the Automatic item 477 msg = new BMessage(CHARSET_CHOICE_MADE); 478 msg->AddInt32("charset", B_MAIL_NULL_CONVERSION); 479 automaticItem = new BMenuItem(B_TRANSLATE("Automatic"), msg); 480 fEncodingMenu->AddItem(automaticItem); 481 fEncodingMenu->AddSeparatorItem(); 482 } 483 484 uint32 defaultCharSet = resending || !fIncoming 485 ? fApp->MailCharacterSet() : B_MAIL_NULL_CONVERSION; 486 bool markedCharSet = false; 487 488 BCharacterSetRoster roster; 489 BCharacterSet charSet; 490 while (roster.GetNextCharacterSet(&charSet) == B_OK) { 491 BString name(charSet.GetPrintName()); 492 const char* mime = charSet.GetMIMEName(); 493 if (mime != NULL) 494 name << " (" << mime << ")"; 495 496 uint32 convertID; 497 if (mime == NULL || strcasecmp(mime, "UTF-8") != 0) 498 convertID = charSet.GetConversionID(); 499 else 500 convertID = B_MAIL_UTF8_CONVERSION; 501 502 msg = new BMessage(CHARSET_CHOICE_MADE); 503 msg->AddInt32("charset", convertID); 504 fEncodingMenu->AddItem(item = new BMenuItem(name.String(), msg)); 505 if (convertID == defaultCharSet && !markedCharSet) { 506 item->SetMarked(true); 507 markedCharSet = true; 508 } 509 } 510 511 msg = new BMessage(CHARSET_CHOICE_MADE); 512 msg->AddInt32("charset", B_MAIL_US_ASCII_CONVERSION); 513 fEncodingMenu->AddItem(item = new BMenuItem("US-ASCII", msg)); 514 if (defaultCharSet == B_MAIL_US_ASCII_CONVERSION && !markedCharSet) { 515 item->SetMarked(true); 516 markedCharSet = true; 517 } 518 519 if (automaticItem != NULL && !markedCharSet) 520 automaticItem->SetMarked(true); 521 522 menu->AddSeparatorItem(); 523 menu->AddItem(fEncodingMenu); 524 fMenuBar->AddItem(menu); 525 fEncodingMenu->SetRadioMode(true); 526 fEncodingMenu->SetTargetForItems(this); 527 528 // Spam Menu 529 530 if (!resending && fIncoming && fApp->ShowSpamGUI()) { 531 menu = new BMenu("Spam filtering"); 532 menu->AddItem(new BMenuItem("Mark as spam and move to trash", 533 new BMessage(M_TRAIN_SPAM_AND_DELETE), 'K')); 534 menu->AddItem(new BMenuItem("Mark as spam", 535 new BMessage(M_TRAIN_SPAM), 'K', B_OPTION_KEY)); 536 menu->AddSeparatorItem(); 537 menu->AddItem(new BMenuItem("Unmark this message", 538 new BMessage(M_UNTRAIN))); 539 menu->AddSeparatorItem(); 540 menu->AddItem(new BMenuItem("Mark as genuine", 541 new BMessage(M_TRAIN_GENUINE), 'K', B_SHIFT_KEY)); 542 fMenuBar->AddItem(menu); 543 } 544 545 // Queries Menu 546 547 fQueryMenu = new BMenu(B_TRANSLATE("Queries")); 548 fMenuBar->AddItem(fQueryMenu); 549 550 _RebuildQueryMenu(true); 551 552 // Button Bar 553 554 BuildToolBar(); 555 556 if (!fApp->ShowToolBar()) 557 fToolBar->Hide(); 558 559 fHeaderView = new THeaderView(fIncoming, resending, 560 fApp->DefaultAccount()); 561 562 fContentView = new TContentView(fIncoming, const_cast<BFont*>(font), 563 false, fApp->ColoredQuotes()); 564 // TContentView needs to be properly const, for now cast away constness 565 566 BLayoutBuilder::Group<>(this, B_VERTICAL, 0) 567 .Add(fMenuBar) 568 .Add(fToolBar) 569 .AddGroup(B_VERTICAL, 0) 570 .Add(fHeaderView) 571 .SetInsets(B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING) 572 .End() 573 .Add(fContentView); 574 575 if (to != NULL) 576 fHeaderView->SetTo(to); 577 578 AddShortcut('n', B_COMMAND_KEY, new BMessage(M_NEW)); 579 580 // If auto-signature, add signature to the text here. 581 582 BString signature = fApp->Signature(); 583 584 if (!fIncoming && strcmp(signature.String(), B_TRANSLATE("None")) != 0) { 585 if (strcmp(signature.String(), B_TRANSLATE("Random")) == 0) 586 PostMessage(M_RANDOM_SIG); 587 else { 588 // Create a query to find this signature 589 BVolume volume; 590 BVolumeRoster().GetBootVolume(&volume); 591 592 BQuery query; 593 query.SetVolume(&volume); 594 query.PushAttr(INDEX_SIGNATURE); 595 query.PushString(signature.String()); 596 query.PushOp(B_EQ); 597 query.Fetch(); 598 599 // If we find the named query, add it to the text. 600 BEntry entry; 601 if (query.GetNextEntry(&entry) == B_NO_ERROR) { 602 BFile file; 603 file.SetTo(&entry, O_RDWR); 604 if (file.InitCheck() == B_NO_ERROR) { 605 entry_ref ref; 606 entry.GetRef(&ref); 607 608 BMessage msg(M_SIGNATURE); 609 msg.AddRef("ref", &ref); 610 PostMessage(&msg); 611 } 612 } else { 613 char tempString [2048]; 614 query.GetPredicate (tempString, sizeof (tempString)); 615 printf ("Query failed, was looking for: %s\n", tempString); 616 } 617 } 618 } 619 620 OpenMessage(ref, _CurrentCharacterSet()); 621 622 AddShortcut('q', B_SHIFT_KEY, new BMessage(kMsgQuitAndKeepAllStatus)); 623 } 624 625 626 BBitmap* 627 TMailWindow::_RetrieveVectorIcon(int32 id) 628 { 629 // Lock access to the list 630 BAutolock lock(sBitmapCacheLock); 631 if (!lock.IsLocked()) 632 return NULL; 633 634 // Check for the bitmap in the cache first 635 BitmapItem* item; 636 for (int32 i = 0; (item = sBitmapCache.ItemAt(i)) != NULL; i++) { 637 if (item->id == id) 638 return item->bm; 639 } 640 641 // If it's not in the cache, try to load it 642 BResources* res = BApplication::AppResources(); 643 if (res == NULL) 644 return NULL; 645 size_t size; 646 const void* data = res->LoadResource(B_VECTOR_ICON_TYPE, id, &size); 647 648 if (!data) 649 return NULL; 650 651 BBitmap* bitmap = new BBitmap(BRect(BPoint(0, 0), 652 be_control_look->ComposeIconSize(22)), B_RGBA32); 653 status_t status = BIconUtils::GetVectorIcon((uint8*)data, size, bitmap); 654 if (status == B_OK) { 655 item = (BitmapItem*)malloc(sizeof(BitmapItem)); 656 item->bm = bitmap; 657 item->id = id; 658 sBitmapCache.AddItem(item); 659 return bitmap; 660 } 661 662 return NULL; 663 } 664 665 666 void 667 TMailWindow::BuildToolBar() 668 { 669 fToolBar = new BToolBar(); 670 fToolBar->AddAction(M_NEW, this, _RetrieveVectorIcon(11), NULL, 671 B_TRANSLATE("New")); 672 fToolBar->AddSeparator(); 673 674 if (fResending) { 675 fToolBar->AddAction(M_SEND_NOW, this, _RetrieveVectorIcon(1), NULL, 676 B_TRANSLATE("Send")); 677 } else if (!fIncoming) { 678 fToolBar->AddAction(M_SEND_NOW, this, _RetrieveVectorIcon(1), NULL, 679 B_TRANSLATE("Send")); 680 fToolBar->SetActionEnabled(M_SEND_NOW, false); 681 fToolBar->AddAction(M_SIG_MENU, this, _RetrieveVectorIcon(2), NULL, 682 B_TRANSLATE("Signature")); 683 fToolBar->AddAction(M_SAVE_AS_DRAFT, this, _RetrieveVectorIcon(3), NULL, 684 B_TRANSLATE("Save")); 685 fToolBar->SetActionEnabled(M_SAVE_AS_DRAFT, false); 686 fToolBar->AddAction(M_PRINT, this, _RetrieveVectorIcon(5), NULL, 687 B_TRANSLATE("Print")); 688 fToolBar->SetActionEnabled(M_PRINT, false); 689 fToolBar->AddAction(M_DELETE, this, _RetrieveVectorIcon(4), NULL, 690 B_TRANSLATE("Trash")); 691 } else { 692 fToolBar->AddAction(M_REPLY, this, _RetrieveVectorIcon(8), NULL, 693 B_TRANSLATE("Reply")); 694 fToolBar->AddAction(M_FORWARD, this, _RetrieveVectorIcon(9), NULL, 695 B_TRANSLATE("Forward")); 696 fToolBar->AddAction(M_PRINT, this, _RetrieveVectorIcon(5), NULL, 697 B_TRANSLATE("Print")); 698 fToolBar->AddAction(M_DELETE_NEXT, this, _RetrieveVectorIcon(4), NULL, 699 B_TRANSLATE("Trash")); 700 if (fApp->ShowSpamGUI()) { 701 fToolBar->AddAction(M_SPAM_BUTTON, this, _RetrieveVectorIcon(10), 702 NULL, B_TRANSLATE("Spam")); 703 } 704 fToolBar->AddSeparator(); 705 fToolBar->AddAction(M_NEXTMSG, this, _RetrieveVectorIcon(6), NULL, 706 B_TRANSLATE("Next")); 707 fToolBar->AddAction(M_UNREAD, this, _RetrieveVectorIcon(12), NULL, 708 B_TRANSLATE("Unread")); 709 fToolBar->SetActionVisible(M_UNREAD, false); 710 fToolBar->AddAction(M_READ, this, _RetrieveVectorIcon(13), NULL, 711 B_TRANSLATE(" Read ")); 712 fToolBar->SetActionVisible(M_READ, false); 713 fToolBar->AddAction(M_PREVMSG, this, _RetrieveVectorIcon(7), NULL, 714 B_TRANSLATE("Previous")); 715 716 if (!fTrackerMessenger.IsValid()) { 717 fToolBar->SetActionEnabled(M_NEXTMSG, false); 718 fToolBar->SetActionEnabled(M_PREVMSG, false); 719 } 720 721 if (!fAutoMarkRead) 722 _AddReadButton(); 723 } 724 fToolBar->AddGlue(); 725 } 726 727 728 void 729 TMailWindow::UpdateViews() 730 { 731 uint8 showToolBar = fApp->ShowToolBar(); 732 733 // Show/Hide Button Bar 734 if (showToolBar) { 735 if (fToolBar->IsHidden()) 736 fToolBar->Show(); 737 738 bool showLabel = showToolBar == kShowToolBar; 739 _UpdateLabel(M_NEW, B_TRANSLATE("New"), showLabel); 740 _UpdateLabel(M_SEND_NOW, B_TRANSLATE("Send"), showLabel); 741 _UpdateLabel(M_SIG_MENU, B_TRANSLATE("Signature"), showLabel); 742 _UpdateLabel(M_SAVE_AS_DRAFT, B_TRANSLATE("Save"), showLabel); 743 _UpdateLabel(M_PRINT, B_TRANSLATE("Print"), showLabel); 744 _UpdateLabel(M_DELETE, B_TRANSLATE("Trash"), showLabel); 745 _UpdateLabel(M_REPLY, B_TRANSLATE("Reply"), showLabel); 746 _UpdateLabel(M_FORWARD, B_TRANSLATE("Forward"), showLabel); 747 _UpdateLabel(M_DELETE_NEXT, B_TRANSLATE("Trash"), showLabel); 748 _UpdateLabel(M_SPAM_BUTTON, B_TRANSLATE("Spam"), showLabel); 749 _UpdateLabel(M_NEXTMSG, B_TRANSLATE("Next"), showLabel); 750 _UpdateLabel(M_UNREAD, B_TRANSLATE("Unread"), showLabel); 751 _UpdateLabel(M_READ, B_TRANSLATE(" Read "), showLabel); 752 _UpdateLabel(M_PREVMSG, B_TRANSLATE("Previous"), showLabel); 753 } else if (!fToolBar->IsHidden()) 754 fToolBar->Hide(); 755 } 756 757 758 void 759 TMailWindow::UpdatePreferences() 760 { 761 fAutoMarkRead = fApp->AutoMarkRead(); 762 763 _UpdateReadButton(); 764 } 765 766 767 TMailWindow::~TMailWindow() 768 { 769 fApp->SetLastWindowFrame(Frame()); 770 771 delete fMail; 772 delete fPanel; 773 delete fOriginatingWindow; 774 delete fRef; 775 776 BAutolock locker(sWindowListLock); 777 sWindowList.RemoveItem(this); 778 } 779 780 781 status_t 782 TMailWindow::GetMailNodeRef(node_ref& nodeRef) const 783 { 784 if (fRef == NULL) 785 return B_ERROR; 786 787 BNode node(fRef); 788 return node.GetNodeRef(&nodeRef); 789 } 790 791 792 bool 793 TMailWindow::GetTrackerWindowFile(entry_ref* ref, bool next) const 794 { 795 // Position was already saved 796 if (next && fNextTrackerPositionSaved) { 797 *ref = fNextRef; 798 return true; 799 } 800 if (!next && fPrevTrackerPositionSaved) { 801 *ref = fPrevRef; 802 return true; 803 } 804 805 if (!fTrackerMessenger.IsValid()) 806 return false; 807 808 // Ask the tracker what the next/prev file in the window is. 809 // Continue asking for the next reference until a valid 810 // email file is found (ignoring other types). 811 entry_ref nextRef = *ref; 812 bool foundRef = false; 813 while (!foundRef) { 814 BMessage request(B_GET_PROPERTY); 815 BMessage spc; 816 if (next) 817 spc.what = 'snxt'; 818 else 819 spc.what = 'sprv'; 820 821 spc.AddString("property", "Entry"); 822 spc.AddRef("data", &nextRef); 823 824 request.AddSpecifier(&spc); 825 BMessage reply; 826 if (fTrackerMessenger.SendMessage(&request, &reply) != B_OK) 827 return false; 828 829 if (reply.FindRef("result", &nextRef) != B_OK) 830 return false; 831 832 char fileType[256]; 833 BNode node(&nextRef); 834 if (node.InitCheck() != B_OK) 835 return false; 836 837 if (BNodeInfo(&node).GetType(fileType) != B_OK) 838 return false; 839 840 if (strcasecmp(fileType, B_MAIL_TYPE) == 0 841 || strcasecmp(fileType, B_PARTIAL_MAIL_TYPE) == 0) 842 foundRef = true; 843 } 844 845 *ref = nextRef; 846 return foundRef; 847 } 848 849 850 void 851 TMailWindow::SaveTrackerPosition(entry_ref* ref) 852 { 853 // if only one of them is saved, we're not going to do it again 854 if (fNextTrackerPositionSaved || fPrevTrackerPositionSaved) 855 return; 856 857 fNextRef = fPrevRef = *ref; 858 859 fNextTrackerPositionSaved = GetTrackerWindowFile(&fNextRef, true); 860 fPrevTrackerPositionSaved = GetTrackerWindowFile(&fPrevRef, false); 861 } 862 863 864 void 865 TMailWindow::SetOriginatingWindow(BWindow* window) 866 { 867 delete fOriginatingWindow; 868 fOriginatingWindow = new BMessenger(window); 869 } 870 871 872 void 873 TMailWindow::SetTrackerSelectionToCurrent() 874 { 875 BMessage setSelection(B_SET_PROPERTY); 876 setSelection.AddSpecifier("Selection"); 877 setSelection.AddRef("data", fRef); 878 879 fTrackerMessenger.SendMessage(&setSelection); 880 } 881 882 883 void 884 TMailWindow::PreserveReadingPos(bool save) 885 { 886 BScrollBar* scroll = fContentView->TextView()->ScrollBar(B_VERTICAL); 887 if (scroll == NULL || fRef == NULL) 888 return; 889 890 BNode node(fRef); 891 float pos = scroll->Value(); 892 893 const char* name = "MAIL:read_pos"; 894 if (save) { 895 node.WriteAttr(name, B_FLOAT_TYPE, 0, &pos, sizeof(pos)); 896 return; 897 } 898 899 if (node.ReadAttr(name, B_FLOAT_TYPE, 0, &pos, sizeof(pos)) == sizeof(pos)) { 900 Lock(); 901 scroll->SetValue(pos); 902 Unlock(); 903 } 904 } 905 906 907 void 908 TMailWindow::MarkMessageRead(entry_ref* message, read_flags flag) 909 { 910 BNode node(message); 911 status_t status = node.InitCheck(); 912 if (status != B_OK) 913 return; 914 915 int32 account; 916 if (node.ReadAttr(B_MAIL_ATTR_ACCOUNT_ID, B_INT32_TYPE, 0, &account, 917 sizeof(account)) < 0) 918 account = -1; 919 920 // don't wait for the server write the attribute directly 921 write_read_attr(node, flag); 922 923 // preserve the read position in the node attribute 924 PreserveReadingPos(true); 925 926 BMailDaemon().MarkAsRead(account, *message, flag); 927 } 928 929 930 void 931 TMailWindow::FrameResized(float width, float height) 932 { 933 fContentView->FrameResized(width, height); 934 } 935 936 937 void 938 TMailWindow::MenusBeginning() 939 { 940 int32 finish = 0; 941 int32 start = 0; 942 943 if (!fIncoming) { 944 bool gotToField = !fHeaderView->IsToEmpty(); 945 bool gotCcField = !fHeaderView->IsCcEmpty(); 946 bool gotBccField = !fHeaderView->IsBccEmpty(); 947 bool gotSubjectField = !fHeaderView->IsSubjectEmpty(); 948 bool gotText = fContentView->TextView()->Text()[0] != 0; 949 fSendNow->SetEnabled(gotToField || gotBccField); 950 fSendLater->SetEnabled(fChanged && (gotToField || gotCcField 951 || gotBccField || gotSubjectField || gotText)); 952 953 be_clipboard->Lock(); 954 fPaste->SetEnabled(be_clipboard->Data()->HasData("text/plain", 955 B_MIME_TYPE) 956 && (fEnclosuresView == NULL || !fEnclosuresView->fList->IsFocus())); 957 be_clipboard->Unlock(); 958 959 fQuote->SetEnabled(false); 960 fRemoveQuote->SetEnabled(false); 961 962 fAdd->SetEnabled(true); 963 fRemove->SetEnabled(fEnclosuresView != NULL 964 && fEnclosuresView->fList->CurrentSelection() >= 0); 965 } else { 966 if (fResending) { 967 bool enable = !fHeaderView->IsToEmpty(); 968 fSendNow->SetEnabled(enable); 969 //fSendLater->SetEnabled(enable); 970 971 if (fHeaderView->ToControl()->HasFocus()) { 972 fHeaderView->ToControl()->GetSelection(&start, &finish); 973 974 fCut->SetEnabled(start != finish); 975 be_clipboard->Lock(); 976 fPaste->SetEnabled(be_clipboard->Data()->HasData( 977 "text/plain", B_MIME_TYPE)); 978 be_clipboard->Unlock(); 979 } else { 980 fCut->SetEnabled(false); 981 fPaste->SetEnabled(false); 982 } 983 } else { 984 fCut->SetEnabled(false); 985 fPaste->SetEnabled(false); 986 } 987 } 988 989 fPrint->SetEnabled(fContentView->TextView()->TextLength()); 990 991 BTextView* textView = dynamic_cast<BTextView*>(CurrentFocus()); 992 if (textView != NULL 993 && (dynamic_cast<AddressTextControl*>(textView->Parent()) != NULL 994 || dynamic_cast<BTextControl*>(textView->Parent()) != NULL)) { 995 // one of To:, Subject:, Account:, Cc:, Bcc: 996 textView->GetSelection(&start, &finish); 997 } else if (fContentView->TextView()->IsFocus()) { 998 fContentView->TextView()->GetSelection(&start, &finish); 999 if (!fIncoming) { 1000 fQuote->SetEnabled(true); 1001 fRemoveQuote->SetEnabled(true); 1002 } 1003 } 1004 1005 fCopy->SetEnabled(start != finish); 1006 if (!fIncoming) 1007 fCut->SetEnabled(start != finish); 1008 1009 // Undo stuff 1010 bool isRedo = false; 1011 undo_state undoState = B_UNDO_UNAVAILABLE; 1012 1013 BTextView* focusTextView = dynamic_cast<BTextView*>(CurrentFocus()); 1014 if (focusTextView != NULL) 1015 undoState = focusTextView->UndoState(&isRedo); 1016 1017 // fUndo->SetLabel((isRedo) 1018 // ? kRedoStrings[undoState] : kUndoStrings[undoState]); 1019 fUndo->SetEnabled(undoState != B_UNDO_UNAVAILABLE); 1020 1021 if (fLeaveStatusMenu != NULL && fRef != NULL) { 1022 BFile file(fRef, B_READ_ONLY); 1023 BString status; 1024 file.ReadAttrString(B_MAIL_ATTR_STATUS, &status); 1025 1026 BMenuItem* LeaveStatus = fLeaveStatusMenu->FindItem(B_QUIT_REQUESTED); 1027 if (LeaveStatus == NULL) 1028 LeaveStatus = fLeaveStatusMenu->FindItem(kMsgQuitAndKeepAllStatus); 1029 1030 if (LeaveStatus != NULL && status.Length() > 0) { 1031 BString label; 1032 label.SetToFormat(B_TRANSLATE("Leave as '%s'"), status.String()); 1033 LeaveStatus->SetLabel(label.String()); 1034 } 1035 } 1036 } 1037 1038 1039 void 1040 TMailWindow::MessageReceived(BMessage* msg) 1041 { 1042 bool wasReadMsg = false; 1043 switch (msg->what) { 1044 case B_MAIL_BODY_FETCHED: 1045 { 1046 status_t status = msg->FindInt32("status"); 1047 if (status != B_OK) { 1048 fprintf(stderr, "Body could not be fetched: %s\n", strerror(status)); 1049 PostMessage(B_QUIT_REQUESTED); 1050 break; 1051 } 1052 1053 entry_ref ref; 1054 if (msg->FindRef("ref", &ref) != B_OK) 1055 break; 1056 if (ref != *fRef) 1057 break; 1058 1059 // reload the current message 1060 OpenMessage(&ref, _CurrentCharacterSet()); 1061 break; 1062 } 1063 1064 case FIELD_CHANGED: 1065 { 1066 int32 prevState = fFieldState; 1067 int32 fieldMask = msg->FindInt32("bitmask"); 1068 void* source; 1069 1070 if (msg->FindPointer("source", &source) == B_OK) { 1071 int32 length; 1072 1073 if (fieldMask == FIELD_BODY) 1074 length = ((TTextView*)source)->TextLength(); 1075 else 1076 length = ((AddressTextControl*)source)->TextLength(); 1077 1078 if (length) 1079 fFieldState |= fieldMask; 1080 else 1081 fFieldState &= ~fieldMask; 1082 } 1083 1084 // Has anything changed? 1085 if (prevState != fFieldState || !fChanged) { 1086 // Change Buttons to reflect this 1087 fToolBar->SetActionEnabled(M_SAVE_AS_DRAFT, fFieldState); 1088 fToolBar->SetActionEnabled(M_PRINT, fFieldState); 1089 fToolBar->SetActionEnabled(M_SEND_NOW, (fFieldState & FIELD_TO) 1090 || (fFieldState & FIELD_BCC)); 1091 } 1092 fChanged = true; 1093 1094 // Update title bar if "subject" has changed 1095 if (!fIncoming && (fieldMask & FIELD_SUBJECT) != 0) { 1096 // If no subject, set to "Mail" 1097 if (fHeaderView->IsSubjectEmpty()) 1098 SetTitle(B_TRANSLATE_SYSTEM_NAME("Mail")); 1099 else 1100 SetTitle(fHeaderView->Subject()); 1101 } 1102 break; 1103 } 1104 case LIST_INVOKED: 1105 PostMessage(msg, fEnclosuresView); 1106 break; 1107 1108 case CHANGE_FONT: 1109 PostMessage(msg, fContentView); 1110 break; 1111 1112 case M_NEW: 1113 { 1114 BMessage message(M_NEW); 1115 message.AddInt32("type", msg->what); 1116 be_app->PostMessage(&message); 1117 break; 1118 } 1119 1120 case M_SPAM_BUTTON: 1121 { 1122 /* 1123 A popup from a button is good only when the behavior has some 1124 consistency and there is some visual indication that a menu 1125 will be shown when clicked. A workable implementation would 1126 have an extra button attached to the main one which has a 1127 downward-pointing arrow. Mozilla Thunderbird's 'Get Mail' 1128 button is a good example of this. 1129 1130 TODO: Replace this code with a split toolbar button 1131 */ 1132 uint32 buttons; 1133 if (msg->FindInt32("buttons", (int32*)&buttons) == B_OK 1134 && buttons == B_SECONDARY_MOUSE_BUTTON) { 1135 BPopUpMenu menu("Spam Actions", false, false); 1136 for (int i = 0; i < 4; i++) 1137 menu.AddItem(new BMenuItem(kSpamMenuItemTextArray[i], 1138 new BMessage(M_TRAIN_SPAM_AND_DELETE + i))); 1139 1140 BPoint where; 1141 msg->FindPoint("where", &where); 1142 BMenuItem* item; 1143 if ((item = menu.Go(where, false, false)) != NULL) 1144 PostMessage(item->Message()); 1145 break; 1146 } else { 1147 // Default action for left clicking on the spam button. 1148 PostMessage(new BMessage(M_TRAIN_SPAM_AND_DELETE)); 1149 } 1150 break; 1151 } 1152 1153 case M_TRAIN_SPAM_AND_DELETE: 1154 PostMessage(M_DELETE_NEXT); 1155 case M_TRAIN_SPAM: 1156 TrainMessageAs("Spam"); 1157 break; 1158 1159 case M_UNTRAIN: 1160 TrainMessageAs("Uncertain"); 1161 break; 1162 1163 case M_TRAIN_GENUINE: 1164 TrainMessageAs("Genuine"); 1165 break; 1166 1167 case M_REPLY: 1168 { 1169 // TODO: This needs removed in favor of a split toolbar button. 1170 // See comments for Spam button 1171 uint32 buttons; 1172 if (msg->FindInt32("buttons", (int32*)&buttons) == B_OK 1173 && buttons == B_SECONDARY_MOUSE_BUTTON) { 1174 BPopUpMenu menu("Reply To", false, false); 1175 menu.AddItem(new BMenuItem(B_TRANSLATE("Reply"), 1176 new BMessage(M_REPLY))); 1177 menu.AddItem(new BMenuItem(B_TRANSLATE("Reply to sender"), 1178 new BMessage(M_REPLY_TO_SENDER))); 1179 menu.AddItem(new BMenuItem(B_TRANSLATE("Reply to all"), 1180 new BMessage(M_REPLY_ALL))); 1181 1182 BPoint where; 1183 msg->FindPoint("where", &where); 1184 1185 BMenuItem* item; 1186 if ((item = menu.Go(where, false, false)) != NULL) { 1187 item->SetTarget(this); 1188 PostMessage(item->Message()); 1189 } 1190 break; 1191 } 1192 // Fall through 1193 } 1194 case M_FORWARD: 1195 { 1196 // TODO: This needs removed in favor of a split toolbar button. 1197 // See comments for Spam button 1198 uint32 buttons; 1199 if (msg->FindInt32("buttons", (int32*)&buttons) == B_OK 1200 && buttons == B_SECONDARY_MOUSE_BUTTON) { 1201 BPopUpMenu menu("Forward", false, false); 1202 menu.AddItem(new BMenuItem(B_TRANSLATE("Forward"), 1203 new BMessage(M_FORWARD))); 1204 menu.AddItem(new BMenuItem( 1205 B_TRANSLATE("Forward without attachments"), 1206 new BMessage(M_FORWARD_WITHOUT_ATTACHMENTS))); 1207 1208 BPoint where; 1209 msg->FindPoint("where", &where); 1210 1211 BMenuItem* item; 1212 if ((item = menu.Go(where, false, false)) != NULL) { 1213 item->SetTarget(this); 1214 PostMessage(item->Message()); 1215 } 1216 break; 1217 } 1218 } 1219 1220 // Fall Through 1221 case M_REPLY_ALL: 1222 case M_REPLY_TO_SENDER: 1223 case M_FORWARD_WITHOUT_ATTACHMENTS: 1224 case M_RESEND: 1225 case M_COPY_TO_NEW: 1226 { 1227 BMessage message(M_NEW); 1228 message.AddRef("ref", fRef); 1229 message.AddPointer("window", this); 1230 message.AddInt32("type", msg->what); 1231 be_app->PostMessage(&message); 1232 break; 1233 } 1234 case M_DELETE: 1235 case M_DELETE_PREV: 1236 case M_DELETE_NEXT: 1237 { 1238 if (msg->what == M_DELETE_NEXT && (modifiers() & B_SHIFT_KEY) != 0) 1239 msg->what = M_DELETE_PREV; 1240 1241 bool foundRef = false; 1242 entry_ref nextRef; 1243 if ((msg->what == M_DELETE_PREV || msg->what == M_DELETE_NEXT) 1244 && fRef != NULL) { 1245 // Find the next message that should be displayed 1246 nextRef = *fRef; 1247 foundRef = GetTrackerWindowFile(&nextRef, 1248 msg->what == M_DELETE_NEXT); 1249 } 1250 if (fIncoming) { 1251 read_flags flag = (fAutoMarkRead == true) ? B_READ : B_SEEN; 1252 MarkMessageRead(fRef, flag); 1253 } 1254 1255 if (!fTrackerMessenger.IsValid() || !fIncoming) { 1256 // Not associated with a tracker window. Create a new 1257 // messenger and ask the tracker to delete this entry 1258 if (fDraft || fIncoming) { 1259 BMessenger tracker("application/x-vnd.Be-TRAK"); 1260 if (tracker.IsValid()) { 1261 BMessage msg('Ttrs'); 1262 msg.AddRef("refs", fRef); 1263 tracker.SendMessage(&msg); 1264 } else { 1265 BAlert* alert = new BAlert("", 1266 B_TRANSLATE("Need Tracker to move items to trash"), 1267 B_TRANSLATE("Sorry")); 1268 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1269 alert->Go(); 1270 } 1271 } 1272 } else { 1273 // This is associated with a tracker window. Ask the 1274 // window to delete this entry. Do it this way if we 1275 // can instead of the above way because it doesn't reset 1276 // the selection (even though we set selection below, this 1277 // still causes problems). 1278 BMessage delmsg(B_DELETE_PROPERTY); 1279 BMessage entryspec('sref'); 1280 entryspec.AddRef("refs", fRef); 1281 entryspec.AddString("property", "Entry"); 1282 delmsg.AddSpecifier(&entryspec); 1283 fTrackerMessenger.SendMessage(&delmsg); 1284 } 1285 1286 // If the next file was found, open it. If it was not, 1287 // we have no choice but to close this window. 1288 if (foundRef) { 1289 TMailWindow* window 1290 = static_cast<TMailApp*>(be_app)->FindWindow(nextRef); 1291 if (window == NULL) 1292 OpenMessage(&nextRef, _CurrentCharacterSet()); 1293 else 1294 window->Activate(); 1295 1296 SetTrackerSelectionToCurrent(); 1297 1298 if (window == NULL) 1299 break; 1300 } 1301 1302 fSent = true; 1303 BMessage msg(B_CLOSE_REQUESTED); 1304 PostMessage(&msg); 1305 break; 1306 } 1307 1308 case M_CLOSE_READ: 1309 { 1310 BMessage message(B_CLOSE_REQUESTED); 1311 message.AddString("status", "Read"); 1312 PostMessage(&message); 1313 break; 1314 } 1315 case M_CLOSE_SAVED: 1316 { 1317 BMessage message(B_QUIT_REQUESTED); 1318 message.AddString("status", "Saved"); 1319 PostMessage(&message); 1320 break; 1321 } 1322 case kMsgQuitAndKeepAllStatus: 1323 fKeepStatusOnQuit = true; 1324 be_app->PostMessage(B_QUIT_REQUESTED); 1325 break; 1326 case M_CLOSE_CUSTOM: 1327 if (msg->HasString("status")) { 1328 BMessage message(B_CLOSE_REQUESTED); 1329 message.AddString("status", msg->GetString("status")); 1330 PostMessage(&message); 1331 } else { 1332 BRect r = Frame(); 1333 BString string = "could not read"; 1334 BNode node(fRef); 1335 if (node.InitCheck() == B_OK) 1336 node.ReadAttrString(B_MAIL_ATTR_STATUS, &string); 1337 1338 new TStatusWindow(r, this, string.String()); 1339 } 1340 break; 1341 1342 case M_STATUS: 1343 { 1344 const char* attribute; 1345 if (msg->FindString("attribute", &attribute) != B_OK) 1346 break; 1347 1348 BMessage message(B_CLOSE_REQUESTED); 1349 message.AddString("status", attribute); 1350 PostMessage(&message); 1351 break; 1352 } 1353 case M_HEADER: 1354 { 1355 bool showHeader = !fHeader->IsMarked(); 1356 fHeader->SetMarked(showHeader); 1357 1358 BMessage message(M_HEADER); 1359 message.AddBool("header", showHeader); 1360 PostMessage(&message, fContentView->TextView()); 1361 break; 1362 } 1363 case M_RAW: 1364 { 1365 bool raw = !(fRaw->IsMarked()); 1366 fRaw->SetMarked(raw); 1367 BMessage message(M_RAW); 1368 message.AddBool("raw", raw); 1369 PostMessage(&message, fContentView->TextView()); 1370 break; 1371 } 1372 case M_SEND_NOW: 1373 case M_SAVE_AS_DRAFT: 1374 Send(msg->what == M_SEND_NOW); 1375 break; 1376 1377 case M_SAVE: 1378 { 1379 const char* address; 1380 const char* name; 1381 if (msg->FindString("address", (const char**)&address) != B_OK) 1382 break; 1383 if (msg->FindString("name", (const char**)&name) != B_OK) 1384 break; 1385 1386 BVolumeRoster volumeRoster; 1387 BVolume volume; 1388 BQuery query; 1389 BEntry entry; 1390 bool foundEntry = false; 1391 1392 char* arg = (char*)malloc(strlen("META:email=") 1393 + strlen(address) + 1); 1394 sprintf(arg, "META:email=%s", address); 1395 1396 // Search a Person file with this email address 1397 while (volumeRoster.GetNextVolume(&volume) == B_NO_ERROR) { 1398 if (!volume.KnowsQuery()) 1399 continue; 1400 1401 query.SetVolume(&volume); 1402 query.SetPredicate(arg); 1403 query.Fetch(); 1404 1405 if (query.GetNextEntry(&entry) == B_NO_ERROR) { 1406 BMessenger tracker("application/x-vnd.Be-TRAK"); 1407 if (tracker.IsValid()) { 1408 entry_ref ref; 1409 entry.GetRef(&ref); 1410 1411 BMessage open(B_REFS_RECEIVED); 1412 open.AddRef("refs", &ref); 1413 tracker.SendMessage(&open); 1414 foundEntry = true; 1415 break; 1416 } 1417 } 1418 // Try next volume, if any 1419 query.Clear(); 1420 } 1421 1422 if (!foundEntry) { 1423 // None found. 1424 // Ask to open a new Person file with this address + name pre-filled 1425 _CreateNewPerson(address, name); 1426 } 1427 free(arg); 1428 break; 1429 } 1430 1431 case M_READ_POS: 1432 PreserveReadingPos(false); 1433 break; 1434 1435 case M_PRINT_SETUP: 1436 PrintSetup(); 1437 break; 1438 1439 case M_PRINT: 1440 Print(); 1441 break; 1442 1443 case M_SELECT: 1444 break; 1445 1446 case M_FIND: 1447 FindWindow::Find(this); 1448 break; 1449 1450 case M_FIND_AGAIN: 1451 FindWindow::FindAgain(this); 1452 break; 1453 1454 case M_ADD_QUOTE_LEVEL: 1455 case M_SUB_QUOTE_LEVEL: 1456 PostMessage(msg->what, fContentView); 1457 break; 1458 1459 case M_RANDOM_SIG: 1460 { 1461 BList sigList; 1462 BMessage *message; 1463 1464 BVolume volume; 1465 BVolumeRoster().GetBootVolume(&volume); 1466 1467 BQuery query; 1468 query.SetVolume(&volume); 1469 1470 char predicate[128]; 1471 sprintf(predicate, "%s = *", INDEX_SIGNATURE); 1472 query.SetPredicate(predicate); 1473 query.Fetch(); 1474 1475 BEntry entry; 1476 while (query.GetNextEntry(&entry) == B_NO_ERROR) { 1477 BFile file(&entry, O_RDONLY); 1478 if (file.InitCheck() == B_NO_ERROR) { 1479 entry_ref ref; 1480 entry.GetRef(&ref); 1481 1482 message = new BMessage(M_SIGNATURE); 1483 message->AddRef("ref", &ref); 1484 sigList.AddItem(message); 1485 } 1486 } 1487 if (sigList.CountItems() > 0) { 1488 srand(time(0)); 1489 PostMessage((BMessage*)sigList.ItemAt(rand() 1490 % sigList.CountItems())); 1491 1492 for (int32 i = 0; (message = (BMessage*)sigList.ItemAt(i)) 1493 != NULL; i++) 1494 delete message; 1495 } 1496 break; 1497 } 1498 case M_SIGNATURE: 1499 { 1500 BMessage message(*msg); 1501 PostMessage(&message, fContentView); 1502 fSigAdded = true; 1503 break; 1504 } 1505 case M_SIG_MENU: 1506 { 1507 TMenu* menu; 1508 BMenuItem* item; 1509 menu = new TMenu("Add Signature", INDEX_SIGNATURE, M_SIGNATURE, 1510 true); 1511 1512 BPoint where; 1513 if (msg->FindPoint("where", &where) != B_OK) { 1514 BRect rect; 1515 BButton* button = fToolBar->FindButton(M_SIG_MENU); 1516 if (button != NULL) 1517 rect = button->Frame(); 1518 else 1519 rect = fToolBar->Bounds(); 1520 1521 where = button->ConvertToScreen(BPoint( 1522 ((rect.right - rect.left) / 2) - 16, 1523 (rect.bottom - rect.top) / 2)); 1524 } 1525 1526 if ((item = menu->Go(where, false, true)) != NULL) { 1527 item->SetTarget(this); 1528 (dynamic_cast<BInvoker*>(item))->Invoke(); 1529 } 1530 delete menu; 1531 break; 1532 } 1533 1534 case M_ADD: 1535 if (!fPanel) { 1536 BMessenger me(this); 1537 BMessage msg(REFS_RECEIVED); 1538 fPanel = new BFilePanel(B_OPEN_PANEL, &me, &fOpenFolder, false, 1539 true, &msg); 1540 } else if (!fPanel->Window()->IsHidden()) { 1541 fPanel->Window()->Activate(); 1542 } 1543 1544 if (fPanel->Window()->IsHidden()) 1545 fPanel->Window()->Show(); 1546 break; 1547 1548 case M_REMOVE: 1549 PostMessage(msg->what, fEnclosuresView); 1550 break; 1551 1552 case CHARSET_CHOICE_MADE: 1553 { 1554 int32 charSet; 1555 if (msg->FindInt32("charset", &charSet) != B_OK) 1556 break; 1557 1558 BMessage update(FIELD_CHANGED); 1559 update.AddInt32("bitmask", 0); 1560 // just enable the save button 1561 PostMessage(&update); 1562 1563 if (fIncoming && !fResending) { 1564 // The user wants to see the message they are reading (not 1565 // composing) displayed with a different kind of character set 1566 // for decoding. Reload the whole message and redisplay. For 1567 // messages which are being composed, the character set is 1568 // retrieved from the header view when it is needed. 1569 1570 entry_ref fileRef = *fRef; 1571 OpenMessage(&fileRef, charSet); 1572 } 1573 break; 1574 } 1575 1576 case B_SIMPLE_DATA: 1577 case REFS_RECEIVED: 1578 AddEnclosure(msg); 1579 break; 1580 1581 // 1582 // Navigation Messages 1583 // 1584 case M_UNREAD: 1585 MarkMessageRead(fRef, B_SEEN); 1586 _UpdateReadButton(); 1587 PostMessage(M_NEXTMSG); 1588 break; 1589 case M_READ: 1590 wasReadMsg = true; 1591 _UpdateReadButton(); 1592 msg->what = M_NEXTMSG; 1593 case M_PREVMSG: 1594 case M_NEXTMSG: 1595 { 1596 if (fRef == NULL) 1597 break; 1598 entry_ref orgRef = *fRef; 1599 entry_ref nextRef = *fRef; 1600 if (GetTrackerWindowFile(&nextRef, (msg->what == M_NEXTMSG))) { 1601 TMailWindow* window = static_cast<TMailApp*>(be_app) 1602 ->FindWindow(nextRef); 1603 if (window == NULL) { 1604 BNode node(fRef); 1605 read_flags currentFlag; 1606 if (read_read_attr(node, currentFlag) != B_OK) 1607 currentFlag = B_UNREAD; 1608 if (fAutoMarkRead == true) 1609 MarkMessageRead(fRef, B_READ); 1610 else if (currentFlag != B_READ && !wasReadMsg) 1611 MarkMessageRead(fRef, B_SEEN); 1612 1613 OpenMessage(&nextRef, _CurrentCharacterSet()); 1614 } else { 1615 window->Activate(); 1616 //fSent = true; 1617 PostMessage(B_CLOSE_REQUESTED); 1618 } 1619 1620 SetTrackerSelectionToCurrent(); 1621 } else { 1622 if (wasReadMsg) 1623 PostMessage(B_CLOSE_REQUESTED); 1624 1625 beep(); 1626 } 1627 if (wasReadMsg) 1628 MarkMessageRead(&orgRef, B_READ); 1629 break; 1630 } 1631 1632 case M_SAVE_POSITION: 1633 if (fRef != NULL) 1634 SaveTrackerPosition(fRef); 1635 break; 1636 1637 case RESET_BUTTONS: 1638 fChanged = false; 1639 fFieldState = 0; 1640 if (!fHeaderView->IsToEmpty()) 1641 fFieldState |= FIELD_TO; 1642 if (!fHeaderView->IsSubjectEmpty()) 1643 fFieldState |= FIELD_SUBJECT; 1644 if (!fHeaderView->IsCcEmpty()) 1645 fFieldState |= FIELD_CC; 1646 if (!fHeaderView->IsBccEmpty()) 1647 fFieldState |= FIELD_BCC; 1648 if (fContentView->TextView()->TextLength() != 0) 1649 fFieldState |= FIELD_BODY; 1650 1651 fToolBar->SetActionEnabled(M_SAVE_AS_DRAFT, false); 1652 fToolBar->SetActionEnabled(M_PRINT, fFieldState); 1653 fToolBar->SetActionEnabled(M_SEND_NOW, (fFieldState & FIELD_TO) 1654 || (fFieldState & FIELD_BCC)); 1655 break; 1656 1657 case M_CHECK_SPELLING: 1658 if (gDictCount == 0) 1659 // Give the application time to init and load dictionaries. 1660 snooze (1500000); 1661 if (!gDictCount) { 1662 beep(); 1663 BAlert* alert = new BAlert("", 1664 B_TRANSLATE("Mail couldn't find its dictionary."), 1665 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, 1666 B_OFFSET_SPACING, B_STOP_ALERT); 1667 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1668 alert->Go(); 1669 } else { 1670 fSpelling->SetMarked(!fSpelling->IsMarked()); 1671 fContentView->TextView()->EnableSpellCheck( 1672 fSpelling->IsMarked()); 1673 } 1674 break; 1675 1676 case M_QUERY_RECIPIENT: 1677 { 1678 BString searchText(fHeaderView->To()); 1679 if (searchText != "") { 1680 _LaunchQuery(kSameRecipientItem, B_MAIL_ATTR_TO, 1681 searchText); 1682 } 1683 break; 1684 } 1685 1686 case M_QUERY_SENDER: 1687 { 1688 BString searchText(fHeaderView->From()); 1689 if (searchText != "") { 1690 _LaunchQuery(kSameSenderItem, B_MAIL_ATTR_FROM, 1691 searchText); 1692 } 1693 break; 1694 } 1695 1696 case M_QUERY_SUBJECT: 1697 { 1698 // If there's no thread attribute (e.g. new mail) use subject 1699 BString searchText(fHeaderView->Subject()); 1700 BNode node(fRef); 1701 if (node.InitCheck() == B_OK) 1702 node.ReadAttrString(B_MAIL_ATTR_THREAD, &searchText); 1703 1704 if (searchText != "") { 1705 // query for subject as sent mails have no thread attribute 1706 _LaunchQuery(kSameSubjectItem, B_MAIL_ATTR_SUBJECT, 1707 searchText); 1708 } 1709 break; 1710 } 1711 case M_EDIT_QUERIES: 1712 { 1713 BPath path; 1714 if (_GetQueryPath(&path) < B_OK) 1715 break; 1716 1717 // the user used this command, make sure the folder actually 1718 // exists - if it didn't inform the user what to do with it 1719 BEntry entry(path.Path()); 1720 bool showAlert = false; 1721 if (!entry.Exists()) { 1722 showAlert = true; 1723 create_directory(path.Path(), 0777); 1724 } 1725 1726 BEntry folderEntry; 1727 if (folderEntry.SetTo(path.Path()) == B_OK 1728 && folderEntry.Exists()) { 1729 BMessage openFolderCommand(B_REFS_RECEIVED); 1730 BMessenger tracker("application/x-vnd.Be-TRAK"); 1731 1732 entry_ref ref; 1733 folderEntry.GetRef(&ref); 1734 openFolderCommand.AddRef("refs", &ref); 1735 tracker.SendMessage(&openFolderCommand); 1736 } 1737 1738 if (showAlert) { 1739 // just some patience before Tracker pops up the folder 1740 snooze(250000); 1741 BAlert* alert = new BAlert(B_TRANSLATE("helpful message"), 1742 B_TRANSLATE("Put your favorite e-mail queries and query " 1743 "templates in this folder."), B_TRANSLATE("OK"), NULL, NULL, 1744 B_WIDTH_AS_USUAL, B_IDEA_ALERT); 1745 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1746 alert->Go(NULL); 1747 } 1748 1749 break; 1750 } 1751 1752 case B_PATH_MONITOR: 1753 _RebuildQueryMenu(); 1754 break; 1755 1756 default: 1757 BWindow::MessageReceived(msg); 1758 } 1759 } 1760 1761 1762 void 1763 TMailWindow::AddEnclosure(BMessage* msg) 1764 { 1765 if (fEnclosuresView == NULL && !fIncoming) { 1766 fEnclosuresView = new TEnclosuresView; 1767 AddChild(fEnclosuresView, fContentView); 1768 } 1769 1770 if (fEnclosuresView == NULL) 1771 return; 1772 1773 if (msg && msg->HasRef("refs")) { 1774 // Add enclosure to view 1775 PostMessage(msg, fEnclosuresView); 1776 1777 fChanged = true; 1778 BEntry entry; 1779 entry_ref ref; 1780 msg->FindRef("refs", &ref); 1781 entry.SetTo(&ref); 1782 entry.GetParent(&entry); 1783 entry.GetRef(&fOpenFolder); 1784 } 1785 } 1786 1787 1788 bool 1789 TMailWindow::QuitRequested() 1790 { 1791 int32 result; 1792 1793 if ((!fIncoming || (fIncoming && fResending)) && fChanged && !fSent 1794 && (!fHeaderView->IsToEmpty() 1795 || !fHeaderView->IsSubjectEmpty() 1796 || !fHeaderView->IsCcEmpty() 1797 || !fHeaderView->IsBccEmpty() 1798 || (fContentView->TextView() != NULL 1799 && strlen(fContentView->TextView()->Text())) 1800 || (fEnclosuresView != NULL 1801 && fEnclosuresView->fList->CountItems()))) { 1802 if (fResending) { 1803 BAlert* alert = new BAlert("", B_TRANSLATE( 1804 "Send this message before closing?"), 1805 B_TRANSLATE("Cancel"), 1806 B_TRANSLATE("Don't send"), 1807 B_TRANSLATE("Send"), 1808 B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT); 1809 alert->SetShortcut(0, B_ESCAPE); 1810 alert->SetShortcut(1, 'd'); 1811 alert->SetShortcut(2, 's'); 1812 result = alert->Go(); 1813 1814 switch (result) { 1815 case 0: // Cancel 1816 return false; 1817 case 1: // Don't send 1818 break; 1819 case 2: // Send 1820 Send(true); 1821 break; 1822 } 1823 } else { 1824 BAlert* alert = new BAlert("", 1825 B_TRANSLATE("Save this message as a draft before closing?"), 1826 B_TRANSLATE("Cancel"), 1827 B_TRANSLATE("Don't save"), 1828 B_TRANSLATE("Save"), 1829 B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT); 1830 alert->SetShortcut(0, B_ESCAPE); 1831 alert->SetShortcut(1, 'd'); 1832 alert->SetShortcut(2, 's'); 1833 result = alert->Go(); 1834 switch (result) { 1835 case 0: // Cancel 1836 return false; 1837 case 1: // Don't Save 1838 break; 1839 case 2: // Save 1840 Send(false); 1841 break; 1842 } 1843 } 1844 } 1845 1846 BMessage message(WINDOW_CLOSED); 1847 message.AddInt32("kind", MAIL_WINDOW); 1848 message.AddPointer("window", this); 1849 be_app->PostMessage(&message); 1850 1851 if (CurrentMessage() && CurrentMessage()->HasString("status")) { 1852 // User explicitly requests a status to set this message to. 1853 if (!CurrentMessage()->HasString("same")) { 1854 const char* status = CurrentMessage()->FindString("status"); 1855 if (status != NULL) { 1856 BNode node(fRef); 1857 if (node.InitCheck() == B_NO_ERROR) { 1858 node.RemoveAttr(B_MAIL_ATTR_STATUS); 1859 WriteAttrString(&node, B_MAIL_ATTR_STATUS, status); 1860 } 1861 } 1862 } 1863 } else if (fRef != NULL && !fKeepStatusOnQuit) { 1864 // ...Otherwise just set the message read 1865 if (fAutoMarkRead == true) 1866 MarkMessageRead(fRef, B_READ); 1867 else { 1868 BNode node(fRef); 1869 read_flags currentFlag; 1870 if (read_read_attr(node, currentFlag) != B_OK) 1871 currentFlag = B_UNREAD; 1872 if (currentFlag == B_UNREAD) 1873 MarkMessageRead(fRef, B_SEEN); 1874 } 1875 } 1876 1877 BPrivate::BPathMonitor::StopWatching(BMessenger(this, this)); 1878 1879 return true; 1880 } 1881 1882 1883 void 1884 TMailWindow::Show() 1885 { 1886 if (Lock()) { 1887 if (!fResending && (fIncoming || fReplying)) { 1888 fContentView->TextView()->MakeFocus(true); 1889 } else { 1890 fHeaderView->ToControl()->MakeFocus(true); 1891 fHeaderView->ToControl()->SelectAll(); 1892 } 1893 Unlock(); 1894 } 1895 BWindow::Show(); 1896 } 1897 1898 1899 void 1900 TMailWindow::Zoom(BPoint /*pos*/, float /*x*/, float /*y*/) 1901 { 1902 float height; 1903 float width; 1904 1905 BRect rect = Frame(); 1906 width = 80 * fApp->ContentFont().StringWidth("M") 1907 + (rect.Width() - fContentView->TextView()->Bounds().Width() + 6); 1908 1909 BScreen screen(this); 1910 BRect screenFrame = screen.Frame(); 1911 if (width > (screenFrame.Width() - 8)) 1912 width = screenFrame.Width() - 8; 1913 1914 height = max_c(fContentView->TextView()->CountLines(), 20) 1915 * fContentView->TextView()->LineHeight(0) 1916 + (rect.Height() - fContentView->TextView()->Bounds().Height()); 1917 if (height > (screenFrame.Height() - 29)) 1918 height = screenFrame.Height() - 29; 1919 1920 rect.right = rect.left + width; 1921 rect.bottom = rect.top + height; 1922 1923 if (abs((int)(Frame().Width() - rect.Width())) < 5 1924 && abs((int)(Frame().Height() - rect.Height())) < 5) { 1925 rect = fZoom; 1926 } else { 1927 fZoom = Frame(); 1928 screenFrame.InsetBy(6, 6); 1929 1930 if (rect.Width() > screenFrame.Width()) 1931 rect.right = rect.left + screenFrame.Width(); 1932 if (rect.Height() > screenFrame.Height()) 1933 rect.bottom = rect.top + screenFrame.Height(); 1934 1935 if (rect.right > screenFrame.right) { 1936 rect.left -= rect.right - screenFrame.right; 1937 rect.right = screenFrame.right; 1938 } 1939 if (rect.bottom > screenFrame.bottom) { 1940 rect.top -= rect.bottom - screenFrame.bottom; 1941 rect.bottom = screenFrame.bottom; 1942 } 1943 if (rect.left < screenFrame.left) { 1944 rect.right += screenFrame.left - rect.left; 1945 rect.left = screenFrame.left; 1946 } 1947 if (rect.top < screenFrame.top) { 1948 rect.bottom += screenFrame.top - rect.top; 1949 rect.top = screenFrame.top; 1950 } 1951 } 1952 1953 ResizeTo(rect.Width(), rect.Height()); 1954 MoveTo(rect.LeftTop()); 1955 } 1956 1957 1958 void 1959 TMailWindow::WindowActivated(bool status) 1960 { 1961 if (status) { 1962 BAutolock locker(sWindowListLock); 1963 sWindowList.RemoveItem(this); 1964 sWindowList.AddItem(this, 0); 1965 } 1966 } 1967 1968 1969 void 1970 TMailWindow::Forward(entry_ref* ref, TMailWindow* window, 1971 bool includeAttachments) 1972 { 1973 BEmailMessage* mail = window->Mail(); 1974 if (mail == NULL) 1975 return; 1976 1977 uint32 useAccountFrom = fApp->UseAccountFrom(); 1978 1979 fMail = mail->ForwardMessage(useAccountFrom == ACCOUNT_FROM_MAIL, 1980 includeAttachments); 1981 1982 BFile file(ref, O_RDONLY); 1983 if (file.InitCheck() < B_NO_ERROR) 1984 return; 1985 1986 fHeaderView->SetSubject(fMail->Subject()); 1987 1988 // set mail account 1989 1990 if (useAccountFrom == ACCOUNT_FROM_MAIL) 1991 fHeaderView->SetAccount(fMail->Account()); 1992 1993 if (fMail->CountComponents() > 1) { 1994 // if there are any enclosures to be added, first add the enclosures 1995 // view to the window 1996 AddEnclosure(NULL); 1997 if (fEnclosuresView) 1998 fEnclosuresView->AddEnclosuresFromMail(fMail); 1999 } 2000 2001 fContentView->TextView()->LoadMessage(fMail, false, NULL); 2002 fChanged = false; 2003 fFieldState = 0; 2004 } 2005 2006 2007 void 2008 TMailWindow::Print() 2009 { 2010 BPrintJob print(Title()); 2011 2012 if (!fApp->HasPrintSettings()) { 2013 if (print.Settings()) { 2014 fApp->SetPrintSettings(print.Settings()); 2015 } else { 2016 PrintSetup(); 2017 if (!fApp->HasPrintSettings()) 2018 return; 2019 } 2020 } 2021 2022 print.SetSettings(new BMessage(fApp->PrintSettings())); 2023 2024 if (print.ConfigJob() == B_OK) { 2025 int32 curPage = 1; 2026 int32 lastLine = 0; 2027 BTextView header_view(print.PrintableRect(), "header", 2028 print.PrintableRect().OffsetByCopy(BPoint( 2029 -print.PrintableRect().left, -print.PrintableRect().top)), 2030 B_FOLLOW_ALL_SIDES); 2031 2032 //---------Init the header fields 2033 #define add_header_field(label, field) { \ 2034 /*header_view.SetFontAndColor(be_bold_font);*/ \ 2035 header_view.Insert(label); \ 2036 header_view.Insert(" "); \ 2037 /*header_view.SetFontAndColor(be_plain_font);*/ \ 2038 header_view.Insert(field); \ 2039 header_view.Insert("\n"); \ 2040 } 2041 2042 add_header_field("Subject:", fHeaderView->Subject()); 2043 add_header_field("To:", fHeaderView->To()); 2044 if (!fHeaderView->IsCcEmpty()) 2045 add_header_field(B_TRANSLATE("Cc:"), fHeaderView->Cc()); 2046 2047 if (!fHeaderView->IsDateEmpty()) 2048 header_view.Insert(fHeaderView->Date()); 2049 2050 int32 maxLine = fContentView->TextView()->CountLines(); 2051 BRect pageRect = print.PrintableRect(); 2052 BRect curPageRect = pageRect; 2053 2054 print.BeginJob(); 2055 float header_height = header_view.TextHeight(0, 2056 header_view.CountLines()); 2057 2058 BRect rect(0, 0, pageRect.Width(), header_height); 2059 BBitmap bmap(rect, B_BITMAP_ACCEPTS_VIEWS, B_RGBA32); 2060 bmap.Lock(); 2061 bmap.AddChild(&header_view); 2062 print.DrawView(&header_view, rect, BPoint(0.0, 0.0)); 2063 HorizontalLine line(BRect(0, 0, pageRect.right, 0)); 2064 bmap.AddChild(&line); 2065 print.DrawView(&line, line.Bounds(), BPoint(0, header_height + 1)); 2066 bmap.Unlock(); 2067 header_height += 5; 2068 2069 do { 2070 int32 lineOffset = fContentView->TextView()->OffsetAt(lastLine); 2071 curPageRect.OffsetTo(0, 2072 fContentView->TextView()->PointAt(lineOffset).y); 2073 2074 int32 fromLine = lastLine; 2075 lastLine = fContentView->TextView()->LineAt( 2076 BPoint(0.0, curPageRect.bottom - ((curPage == 1) 2077 ? header_height : 0))); 2078 2079 float curPageHeight = fContentView->TextView()->TextHeight( 2080 fromLine, lastLine) + (curPage == 1 ? header_height : 0); 2081 2082 if (curPageHeight > pageRect.Height()) { 2083 curPageHeight = fContentView->TextView()->TextHeight( 2084 fromLine, --lastLine) + (curPage == 1 ? header_height : 0); 2085 } 2086 curPageRect.bottom = curPageRect.top + curPageHeight - 1.0; 2087 2088 if (curPage >= print.FirstPage() && curPage <= print.LastPage()) { 2089 print.DrawView(fContentView->TextView(), curPageRect, 2090 BPoint(0.0, curPage == 1 ? header_height : 0.0)); 2091 print.SpoolPage(); 2092 } 2093 2094 curPageRect = pageRect; 2095 lastLine++; 2096 curPage++; 2097 2098 } while (print.CanContinue() && lastLine < maxLine); 2099 2100 print.CommitJob(); 2101 bmap.RemoveChild(&header_view); 2102 bmap.RemoveChild(&line); 2103 } 2104 } 2105 2106 2107 void 2108 TMailWindow::PrintSetup() 2109 { 2110 BPrintJob printJob("mail_print"); 2111 2112 if (fApp->HasPrintSettings()) { 2113 BMessage printSettings = fApp->PrintSettings(); 2114 printJob.SetSettings(new BMessage(printSettings)); 2115 } 2116 2117 if (printJob.ConfigPage() == B_OK) 2118 fApp->SetPrintSettings(printJob.Settings()); 2119 } 2120 2121 2122 void 2123 TMailWindow::SetTo(const char* mailTo, const char* subject, const char* ccTo, 2124 const char* bccTo, const BString* body, BMessage* enclosures) 2125 { 2126 Lock(); 2127 2128 if (mailTo != NULL && mailTo[0]) 2129 fHeaderView->SetTo(mailTo); 2130 if (subject != NULL && subject[0]) 2131 fHeaderView->SetSubject(subject); 2132 if (ccTo != NULL && ccTo[0]) 2133 fHeaderView->SetCc(ccTo); 2134 if (bccTo != NULL && bccTo[0]) 2135 fHeaderView->SetBcc(bccTo); 2136 2137 if (body != NULL && body->Length()) { 2138 fContentView->TextView()->SetText(body->String(), body->Length()); 2139 fContentView->TextView()->GoToLine(0); 2140 } 2141 2142 if (enclosures && enclosures->HasRef("refs")) 2143 AddEnclosure(enclosures); 2144 2145 Unlock(); 2146 } 2147 2148 2149 void 2150 TMailWindow::CopyMessage(entry_ref* ref, TMailWindow* src) 2151 { 2152 BNode file(ref); 2153 if (file.InitCheck() == B_OK) { 2154 BString string; 2155 if (file.ReadAttrString(B_MAIL_ATTR_TO, &string) == B_OK) 2156 fHeaderView->SetTo(string); 2157 2158 if (file.ReadAttrString(B_MAIL_ATTR_SUBJECT, &string) == B_OK) 2159 fHeaderView->SetSubject(string); 2160 2161 if (file.ReadAttrString(B_MAIL_ATTR_CC, &string) == B_OK) 2162 fHeaderView->SetCc(string); 2163 } 2164 2165 TTextView* text = src->fContentView->TextView(); 2166 text_run_array* style = text->RunArray(0, text->TextLength()); 2167 2168 fContentView->TextView()->SetText(text->Text(), text->TextLength(), style); 2169 2170 free(style); 2171 } 2172 2173 2174 void 2175 TMailWindow::Reply(entry_ref* ref, TMailWindow* window, uint32 type) 2176 { 2177 fRepliedMail = *ref; 2178 SetOriginatingWindow(window); 2179 2180 BEmailMessage* mail = window->Mail(); 2181 if (mail == NULL) 2182 return; 2183 2184 if (type == M_REPLY_ALL) 2185 type = B_MAIL_REPLY_TO_ALL; 2186 else if (type == M_REPLY_TO_SENDER) 2187 type = B_MAIL_REPLY_TO_SENDER; 2188 else 2189 type = B_MAIL_REPLY_TO; 2190 2191 uint32 useAccountFrom = fApp->UseAccountFrom(); 2192 2193 fMail = mail->ReplyMessage(mail_reply_to_mode(type), 2194 useAccountFrom == ACCOUNT_FROM_MAIL, QUOTE); 2195 2196 // set header fields 2197 fHeaderView->SetTo(fMail->To()); 2198 fHeaderView->SetCc(fMail->CC()); 2199 fHeaderView->SetSubject(fMail->Subject()); 2200 2201 int32 accountID; 2202 BFile file(window->fRef, B_READ_ONLY); 2203 if (file.ReadAttr("MAIL:reply_with", B_INT32_TYPE, 0, &accountID, 2204 sizeof(int32)) != B_OK) 2205 accountID = -1; 2206 2207 // set mail account 2208 2209 if ((useAccountFrom == ACCOUNT_FROM_MAIL) || (accountID > -1)) { 2210 if (useAccountFrom == ACCOUNT_FROM_MAIL) 2211 fHeaderView->SetAccount(fMail->Account()); 2212 else 2213 fHeaderView->SetAccount(accountID); 2214 } 2215 2216 // create preamble string 2217 2218 BString preamble = fApp->ReplyPreamble(); 2219 2220 BString name; 2221 mail->GetName(&name); 2222 if (name.Length() <= 0) 2223 name = B_TRANSLATE("(Name unavailable)"); 2224 2225 BString address(mail->From()); 2226 if (address.Length() <= 0) 2227 address = B_TRANSLATE("(Address unavailable)"); 2228 2229 BString date(mail->HeaderField("Date")); 2230 if (date.Length() <= 0) 2231 date = B_TRANSLATE("(Date unavailable)"); 2232 2233 preamble.ReplaceAll("%n", name); 2234 preamble.ReplaceAll("%e", address); 2235 preamble.ReplaceAll("%d", date); 2236 preamble.ReplaceAll("\\n", "\n"); 2237 2238 // insert (if selection) or load (if whole mail) message text into text view 2239 2240 int32 finish, start; 2241 window->fContentView->TextView()->GetSelection(&start, &finish); 2242 if (start != finish) { 2243 char* text = (char*)malloc(finish - start + 1); 2244 if (text == NULL) 2245 return; 2246 2247 window->fContentView->TextView()->GetText(start, finish - start, text); 2248 if (text[strlen(text) - 1] != '\n') { 2249 text[strlen(text)] = '\n'; 2250 finish++; 2251 } 2252 fContentView->TextView()->SetText(text, finish - start); 2253 free(text); 2254 2255 finish = fContentView->TextView()->CountLines(); 2256 for (int32 loop = 0; loop < finish; loop++) { 2257 fContentView->TextView()->GoToLine(loop); 2258 fContentView->TextView()->Insert((const char*)QUOTE); 2259 } 2260 2261 if (fApp->ColoredQuotes()) { 2262 const BFont* font = fContentView->TextView()->Font(); 2263 int32 length = fContentView->TextView()->TextLength(); 2264 2265 TextRunArray style(length / 8 + 8); 2266 2267 FillInQuoteTextRuns(fContentView->TextView(), NULL, 2268 fContentView->TextView()->Text(), length, font, &style.Array(), 2269 style.MaxEntries()); 2270 2271 fContentView->TextView()->SetRunArray(0, length, &style.Array()); 2272 } 2273 2274 fContentView->TextView()->GoToLine(0); 2275 if (preamble.Length() > 0) 2276 fContentView->TextView()->Insert(preamble); 2277 } else { 2278 fContentView->TextView()->LoadMessage(mail, true, preamble); 2279 } 2280 2281 fReplying = true; 2282 } 2283 2284 2285 status_t 2286 TMailWindow::Send(bool now) 2287 { 2288 if (!now) { 2289 status_t status = SaveAsDraft(); 2290 if (status != B_OK) { 2291 beep(); 2292 BAlert* alert = new BAlert("", B_TRANSLATE("E-mail draft could " 2293 "not be saved!"), B_TRANSLATE("OK")); 2294 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2295 alert->Go(); 2296 } 2297 return status; 2298 } 2299 2300 uint32 characterSetToUse = _CurrentCharacterSet(); 2301 mail_encoding encodingForBody = quoted_printable; 2302 mail_encoding encodingForHeaders = quoted_printable; 2303 2304 // Set up the encoding to use for converting binary to printable ASCII. 2305 // Normally this will be quoted printable, but for some old software, 2306 // particularly Japanese stuff, they only understand base64. They also 2307 // prefer it for the smaller size. Later on this will be reduced to 7bit 2308 // if the encoded text is just 7bit characters. 2309 if (characterSetToUse == B_SJIS_CONVERSION 2310 || characterSetToUse == B_EUC_CONVERSION) 2311 encodingForBody = base64; 2312 else if (characterSetToUse == B_JIS_CONVERSION 2313 || characterSetToUse == B_MAIL_US_ASCII_CONVERSION 2314 || characterSetToUse == B_ISO1_CONVERSION 2315 || characterSetToUse == B_EUC_KR_CONVERSION) 2316 encodingForBody = eight_bit; 2317 2318 // Using quoted printable headers on almost completely non-ASCII Japanese 2319 // is a waste of time. Besides, some stupid cell phone services need 2320 // base64 in the headers. 2321 if (characterSetToUse == B_SJIS_CONVERSION 2322 || characterSetToUse == B_EUC_CONVERSION 2323 || characterSetToUse == B_JIS_CONVERSION 2324 || characterSetToUse == B_EUC_KR_CONVERSION) 2325 encodingForHeaders = base64; 2326 2327 // Count the number of characters in the message body which aren't in the 2328 // currently selected character set. Also see if the resulting encoded 2329 // text can safely use 7 bit characters. 2330 if (fContentView->TextView()->TextLength() > 0) { 2331 // First do a trial encoding with the user's character set. 2332 int32 converterState = 0; 2333 int32 originalLength; 2334 BString tempString; 2335 int32 tempStringLength; 2336 char* tempStringPntr; 2337 originalLength = fContentView->TextView()->TextLength(); 2338 tempStringLength = originalLength * 6; 2339 // Some character sets bloat up on escape codes 2340 tempStringPntr = tempString.LockBuffer (tempStringLength); 2341 if (tempStringPntr != NULL && mail_convert_from_utf8(characterSetToUse, 2342 fContentView->TextView()->Text(), &originalLength, 2343 tempStringPntr, &tempStringLength, &converterState, 2344 0x1A /* used for unknown characters */) == B_OK) { 2345 // Check for any characters which don't fit in a 7 bit encoding. 2346 int i; 2347 bool has8Bit = false; 2348 for (i = 0; i < tempStringLength; i++) { 2349 if (tempString[i] == 0 || (tempString[i] & 0x80)) { 2350 has8Bit = true; 2351 break; 2352 } 2353 } 2354 if (!has8Bit) 2355 encodingForBody = seven_bit; 2356 tempString.UnlockBuffer (tempStringLength); 2357 2358 // Count up the number of unencoded characters and warn the user 2359 if (fApp->WarnAboutUnencodableCharacters()) { 2360 // TODO: ideally, the encoding should be silently changed to 2361 // one that can express this character 2362 int32 offset = 0; 2363 int count = 0; 2364 while (offset >= 0) { 2365 offset = tempString.FindFirst (0x1A, offset); 2366 if (offset >= 0) { 2367 count++; 2368 offset++; 2369 // Don't get stuck finding the same character again. 2370 } 2371 } 2372 if (count > 0) { 2373 int32 userAnswer; 2374 BString messageString; 2375 BString countString; 2376 countString << count; 2377 messageString << B_TRANSLATE("Your main text contains %ld" 2378 " unencodable characters. Perhaps a different " 2379 "character set would work better? Hit Send to send it " 2380 "anyway " 2381 "(a substitute character will be used in place of " 2382 "the unencodable ones), or choose Cancel to go back " 2383 "and try fixing it up."); 2384 messageString.ReplaceFirst("%ld", countString); 2385 BAlert* alert = new BAlert("Question", messageString.String(), 2386 B_TRANSLATE("Send"), 2387 B_TRANSLATE("Cancel"), 2388 NULL, B_WIDTH_AS_USUAL, B_OFFSET_SPACING, 2389 B_WARNING_ALERT); 2390 alert->SetShortcut(1, B_ESCAPE); 2391 userAnswer = alert->Go(); 2392 2393 if (userAnswer == 1) { 2394 // Cancel was picked. 2395 return -1; 2396 } 2397 } 2398 } 2399 } 2400 } 2401 2402 Hide(); 2403 // depending on the system (and I/O) load, this could take a while 2404 // but the user shouldn't be left waiting 2405 2406 status_t result; 2407 2408 if (fResending) { 2409 BFile file(fRef, O_RDONLY); 2410 result = file.InitCheck(); 2411 if (result == B_OK) { 2412 BEmailMessage mail(&file); 2413 mail.SetTo(fHeaderView->To(), characterSetToUse, 2414 encodingForHeaders); 2415 2416 if (fHeaderView->AccountID() != ~0L) 2417 mail.SendViaAccount(fHeaderView->AccountID()); 2418 2419 result = mail.Send(now); 2420 } 2421 } else { 2422 if (fMail == NULL) 2423 // the mail will be deleted when the window is closed 2424 fMail = new BEmailMessage; 2425 2426 // Had an embarrassing bug where replying to a message and clearing the 2427 // CC field meant that it got sent out anyway, so pass in empty strings 2428 // when changing the header to force it to remove the header. 2429 2430 fMail->SetTo(fHeaderView->To(), characterSetToUse, encodingForHeaders); 2431 fMail->SetSubject(fHeaderView->Subject(), characterSetToUse, 2432 encodingForHeaders); 2433 fMail->SetCC(fHeaderView->Cc(), characterSetToUse, encodingForHeaders); 2434 fMail->SetBCC(fHeaderView->Bcc()); 2435 2436 //--- Add X-Mailer field 2437 { 2438 // get app version 2439 version_info info; 2440 memset(&info, 0, sizeof(version_info)); 2441 2442 app_info appInfo; 2443 if (be_app->GetAppInfo(&appInfo) == B_OK) { 2444 BFile file(&appInfo.ref, B_READ_ONLY); 2445 if (file.InitCheck() == B_OK) { 2446 BAppFileInfo appFileInfo(&file); 2447 if (appFileInfo.InitCheck() == B_OK) 2448 appFileInfo.GetVersionInfo(&info, B_APP_VERSION_KIND); 2449 } 2450 } 2451 2452 char versionString[255]; 2453 sprintf(versionString, 2454 "Mail/Haiku %" B_PRIu32 ".%" B_PRIu32 ".%" B_PRIu32, 2455 info.major, info.middle, info.minor); 2456 fMail->SetHeaderField("X-Mailer", versionString); 2457 } 2458 2459 /****/ 2460 2461 // the content text is always added to make sure there is a mail body 2462 fMail->SetBodyTextTo(""); 2463 fContentView->TextView()->AddAsContent(fMail, fApp->WrapMode(), 2464 characterSetToUse, encodingForBody); 2465 2466 if (fEnclosuresView != NULL) { 2467 TListItem* item; 2468 int32 index = 0; 2469 while ((item = (TListItem*)fEnclosuresView->fList->ItemAt(index++)) 2470 != NULL) { 2471 if (item->Component()) 2472 continue; 2473 2474 // leave out missing enclosures 2475 BEntry entry(item->Ref()); 2476 if (!entry.Exists()) 2477 continue; 2478 2479 fMail->Attach(item->Ref(), fApp->AttachAttributes()); 2480 } 2481 } 2482 if (fHeaderView->AccountID() != ~0L) 2483 fMail->SendViaAccount(fHeaderView->AccountID()); 2484 2485 result = fMail->Send(now); 2486 2487 if (fReplying) { 2488 // Set status of the replied mail 2489 2490 BNode node(&fRepliedMail); 2491 if (node.InitCheck() >= B_OK) { 2492 if (fOriginatingWindow) { 2493 BMessage msg(M_SAVE_POSITION), reply; 2494 fOriginatingWindow->SendMessage(&msg, &reply); 2495 } 2496 WriteAttrString(&node, B_MAIL_ATTR_STATUS, "Replied"); 2497 } 2498 } 2499 } 2500 2501 bool close = false; 2502 BString errorMessage; 2503 2504 switch (result) { 2505 case B_OK: 2506 close = true; 2507 fSent = true; 2508 2509 // If it's a draft, remove the draft file 2510 if (fDraft) { 2511 BEntry entry(fRef); 2512 entry.Remove(); 2513 } 2514 break; 2515 2516 case B_MAIL_NO_DAEMON: 2517 { 2518 close = true; 2519 fSent = true; 2520 2521 BAlert* alert = new BAlert("no daemon", 2522 B_TRANSLATE("The mail_daemon is not running. The message is " 2523 "queued and will be sent when the mail_daemon is started."), 2524 B_TRANSLATE("Start now"), B_TRANSLATE("OK")); 2525 alert->SetShortcut(1, B_ESCAPE); 2526 int32 start = alert->Go(); 2527 2528 if (start == 0) { 2529 BMailDaemon daemon; 2530 result = daemon.Launch(); 2531 if (result == B_OK) { 2532 daemon.SendQueuedMail(); 2533 } else { 2534 errorMessage 2535 << B_TRANSLATE("The mail_daemon could not be " 2536 "started:\n\t") 2537 << strerror(result); 2538 } 2539 } 2540 break; 2541 } 2542 2543 // case B_MAIL_UNKNOWN_HOST: 2544 // case B_MAIL_ACCESS_ERROR: 2545 // sprintf(errorMessage, 2546 // "An error occurred trying to connect with the SMTP " 2547 // "host. Check your SMTP host name."); 2548 // break; 2549 // 2550 // case B_MAIL_NO_RECIPIENT: 2551 // sprintf(errorMessage, 2552 // "You must have either a \"To\" or \"Bcc\" recipient."); 2553 // break; 2554 2555 default: 2556 errorMessage << "An error occurred trying to send mail:\n\t" 2557 << strerror(result); 2558 break; 2559 } 2560 2561 if (result != B_NO_ERROR && result != B_MAIL_NO_DAEMON) { 2562 beep(); 2563 BAlert* alert = new BAlert("", errorMessage.String(), 2564 B_TRANSLATE("OK")); 2565 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2566 alert->Go(); 2567 } 2568 if (close) { 2569 PostMessage(B_QUIT_REQUESTED); 2570 } else { 2571 // The window was hidden earlier 2572 Show(); 2573 } 2574 2575 return result; 2576 } 2577 2578 2579 status_t 2580 TMailWindow::SaveAsDraft() 2581 { 2582 BPath draftPath; 2583 BDirectory dir; 2584 BFile draft; 2585 uint32 flags = 0; 2586 2587 if (fDraft) { 2588 status_t status = draft.SetTo(fRef, 2589 B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 2590 if (status != B_OK) 2591 return status; 2592 } else { 2593 // Get the user home directory 2594 status_t status = find_directory(B_USER_DIRECTORY, &draftPath); 2595 if (status != B_OK) 2596 return status; 2597 2598 // Append the relative path of the draft directory 2599 draftPath.Append(kDraftPath); 2600 2601 // Create the file 2602 status = dir.SetTo(draftPath.Path()); 2603 switch (status) { 2604 // Create the directory if it does not exist 2605 case B_ENTRY_NOT_FOUND: 2606 if ((status = dir.CreateDirectory(draftPath.Path(), &dir)) 2607 != B_OK) 2608 return status; 2609 case B_OK: 2610 { 2611 char fileName[B_FILE_NAME_LENGTH]; 2612 // save as some version of the message's subject 2613 if (fHeaderView->IsSubjectEmpty()) { 2614 strlcpy(fileName, B_TRANSLATE("Untitled"), 2615 sizeof(fileName)); 2616 } else { 2617 strlcpy(fileName, fHeaderView->Subject(), sizeof(fileName)); 2618 } 2619 2620 uint32 originalLength = strlen(fileName); 2621 2622 // convert /, \ and : to - 2623 for (char* bad = fileName; (bad = strchr(bad, '/')) != NULL; 2624 ++bad) { 2625 *bad = '-'; 2626 } 2627 for (char* bad = fileName; (bad = strchr(bad, '\\')) != NULL; 2628 ++bad) { 2629 *bad = '-'; 2630 } 2631 for (char* bad = fileName; (bad = strchr(bad, ':')) != NULL; 2632 ++bad) { 2633 *bad = '-'; 2634 } 2635 2636 // Create the file; if the name exists, find a unique name 2637 flags = B_WRITE_ONLY | B_CREATE_FILE | B_FAIL_IF_EXISTS; 2638 int32 i = 1; 2639 do { 2640 status = draft.SetTo(&dir, fileName, flags); 2641 if (status == B_OK) 2642 break; 2643 char appendix[B_FILE_NAME_LENGTH]; 2644 sprintf(appendix, " %" B_PRId32, i++); 2645 int32 pos = min_c(sizeof(fileName) - strlen(appendix), 2646 originalLength); 2647 sprintf(fileName + pos, "%s", appendix); 2648 } while (status == B_FILE_EXISTS); 2649 if (status != B_OK) 2650 return status; 2651 2652 // Cache the ref 2653 if (fRef == NULL) 2654 fRef = new entry_ref; 2655 BEntry entry(&dir, fileName); 2656 entry.GetRef(fRef); 2657 break; 2658 } 2659 default: 2660 return status; 2661 } 2662 } 2663 2664 // Write the content of the message 2665 draft.Write(fContentView->TextView()->Text(), 2666 fContentView->TextView()->TextLength()); 2667 2668 // Add the header stuff as attributes 2669 WriteAttrString(&draft, B_MAIL_ATTR_NAME, fHeaderView->To()); 2670 WriteAttrString(&draft, B_MAIL_ATTR_TO, fHeaderView->To()); 2671 WriteAttrString(&draft, B_MAIL_ATTR_SUBJECT, fHeaderView->Subject()); 2672 if (!fHeaderView->IsCcEmpty()) 2673 WriteAttrString(&draft, B_MAIL_ATTR_CC, fHeaderView->Cc()); 2674 if (!fHeaderView->IsBccEmpty()) 2675 WriteAttrString(&draft, B_MAIL_ATTR_BCC, fHeaderView->Bcc()); 2676 2677 // Add account 2678 if (fHeaderView->AccountName() != NULL) { 2679 WriteAttrString(&draft, B_MAIL_ATTR_ACCOUNT, 2680 fHeaderView->AccountName()); 2681 } 2682 2683 // Add encoding 2684 BMenuItem* menuItem = fEncodingMenu->FindMarked(); 2685 if (menuItem != NULL) 2686 WriteAttrString(&draft, "MAIL:encoding", menuItem->Label()); 2687 2688 // Add the draft attribute for indexing 2689 uint32 draftAttr = true; 2690 draft.WriteAttr("MAIL:draft", B_INT32_TYPE, 0, &draftAttr, sizeof(uint32)); 2691 2692 // Add Attachment paths in attribute 2693 if (fEnclosuresView != NULL) { 2694 TListItem* item; 2695 BString pathStr; 2696 2697 for (int32 i = 0; (item = (TListItem*)fEnclosuresView->fList->ItemAt(i)) 2698 != NULL; i++) { 2699 if (i > 0) 2700 pathStr.Append(":"); 2701 2702 BEntry entry(item->Ref(), true); 2703 if (!entry.Exists()) 2704 continue; 2705 2706 BPath path; 2707 entry.GetPath(&path); 2708 pathStr.Append(path.Path()); 2709 } 2710 if (pathStr.Length()) 2711 draft.WriteAttrString("MAIL:attachments", &pathStr); 2712 } 2713 2714 // Set the MIME Type of the file 2715 BNodeInfo info(&draft); 2716 info.SetType(kDraftType); 2717 2718 fDraft = true; 2719 fChanged = false; 2720 2721 fToolBar->SetActionEnabled(M_SAVE_AS_DRAFT, false); 2722 2723 return B_OK; 2724 } 2725 2726 2727 status_t 2728 TMailWindow::TrainMessageAs(const char* commandWord) 2729 { 2730 status_t errorCode = -1; 2731 BEntry fileEntry; 2732 BPath filePath; 2733 BMessage replyMessage; 2734 BMessage scriptingMessage; 2735 team_id serverTeam; 2736 2737 if (fRef == NULL) 2738 goto ErrorExit; // Need to have a real file and name. 2739 errorCode = fileEntry.SetTo(fRef, true); 2740 if (errorCode != B_OK) 2741 goto ErrorExit; 2742 errorCode = fileEntry.GetPath(&filePath); 2743 if (errorCode != B_OK) 2744 goto ErrorExit; 2745 fileEntry.Unset(); 2746 2747 // Get a connection to the spam database server. Launch if needed. 2748 2749 if (!fMessengerToSpamServer.IsValid()) { 2750 // Make sure the server is running. 2751 if (!be_roster->IsRunning (kSpamServerSignature)) { 2752 errorCode = be_roster->Launch (kSpamServerSignature); 2753 if (errorCode != B_OK) { 2754 BPath path; 2755 entry_ref ref; 2756 directory_which places[] = {B_SYSTEM_NONPACKAGED_BIN_DIRECTORY, 2757 B_SYSTEM_BIN_DIRECTORY}; 2758 for (int32 i = 0; i < 2; i++) { 2759 find_directory(places[i],&path); 2760 path.Append("spamdbm"); 2761 if (!BEntry(path.Path()).Exists()) 2762 continue; 2763 get_ref_for_path(path.Path(),&ref); 2764 2765 errorCode = be_roster->Launch(&ref); 2766 if (errorCode == B_OK) 2767 break; 2768 } 2769 if (errorCode != B_OK) 2770 goto ErrorExit; 2771 } 2772 } 2773 2774 // Set up the messenger to the database server. 2775 errorCode = B_SERVER_NOT_FOUND; 2776 serverTeam = be_roster->TeamFor(kSpamServerSignature); 2777 if (serverTeam < 0) 2778 goto ErrorExit; 2779 2780 fMessengerToSpamServer = BMessenger (kSpamServerSignature, serverTeam, 2781 &errorCode); 2782 2783 if (!fMessengerToSpamServer.IsValid()) 2784 goto ErrorExit; 2785 } 2786 2787 // Ask the server to train on the message. Give it the command word and 2788 // the absolute path name to use. 2789 2790 scriptingMessage.MakeEmpty(); 2791 scriptingMessage.what = B_SET_PROPERTY; 2792 scriptingMessage.AddSpecifier(commandWord); 2793 errorCode = scriptingMessage.AddData("data", B_STRING_TYPE, 2794 filePath.Path(), strlen(filePath.Path()) + 1, false); 2795 if (errorCode != B_OK) 2796 goto ErrorExit; 2797 replyMessage.MakeEmpty(); 2798 errorCode = fMessengerToSpamServer.SendMessage(&scriptingMessage, 2799 &replyMessage); 2800 if (errorCode != B_OK 2801 || replyMessage.FindInt32("error", &errorCode) != B_OK 2802 || errorCode != B_OK) 2803 goto ErrorExit; // Classification failed in one of many ways. 2804 2805 SetTitleForMessage(); 2806 // Update window title to show new spam classification. 2807 return B_OK; 2808 2809 ErrorExit: 2810 beep(); 2811 char errorString[1500]; 2812 snprintf(errorString, sizeof(errorString), "Unable to train the message " 2813 "file \"%s\" as %s. Possibly useful error code: %s (%" B_PRId32 ").", 2814 filePath.Path(), commandWord, strerror(errorCode), errorCode); 2815 BAlert* alert = new BAlert("", errorString, B_TRANSLATE("OK")); 2816 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2817 alert->Go(); 2818 2819 return errorCode; 2820 } 2821 2822 2823 void 2824 TMailWindow::SetTitleForMessage() 2825 { 2826 // Figure out the title of this message and set the title bar 2827 BString title = B_TRANSLATE_SYSTEM_NAME("Mail"); 2828 2829 if (fIncoming) { 2830 if (fMail->GetName(&title) == B_OK) 2831 title << ": \"" << fMail->Subject() << "\""; 2832 else 2833 title = fMail->Subject(); 2834 2835 if (fDownloading) 2836 title.Prepend("Downloading: "); 2837 2838 if (fApp->ShowSpamGUI() && fRef != NULL) { 2839 BString classification; 2840 BNode node(fRef); 2841 char numberString[30]; 2842 BString oldTitle(title); 2843 float spamRatio; 2844 if (node.InitCheck() != B_OK || node.ReadAttrString( 2845 "MAIL:classification", &classification) != B_OK) 2846 classification = "Unrated"; 2847 if (classification != "Spam" && classification != "Genuine") { 2848 // Uncertain, Unrated and other unknown classes, show the ratio. 2849 if (node.InitCheck() == B_OK && node.ReadAttr("MAIL:ratio_spam", 2850 B_FLOAT_TYPE, 0, &spamRatio, sizeof(spamRatio)) 2851 == sizeof(spamRatio)) { 2852 sprintf(numberString, "%.4f", spamRatio); 2853 classification << " " << numberString; 2854 } 2855 } 2856 title = ""; 2857 title << "[" << classification << "] " << oldTitle; 2858 } 2859 } 2860 SetTitle(title); 2861 } 2862 2863 2864 /*! Open *another* message in the existing mail window. Some code here is 2865 duplicated from various constructors. 2866 TODO: The duplicated code should be moved to a private initializer method 2867 */ 2868 status_t 2869 TMailWindow::OpenMessage(const entry_ref* ref, uint32 characterSetForDecoding) 2870 { 2871 if (ref == NULL) 2872 return B_ERROR; 2873 2874 // Set some references to the email file 2875 delete fRef; 2876 fRef = new entry_ref(*ref); 2877 2878 fPrevTrackerPositionSaved = false; 2879 fNextTrackerPositionSaved = false; 2880 2881 fContentView->TextView()->StopLoad(); 2882 delete fMail; 2883 fMail = NULL; 2884 2885 BFile file(fRef, B_READ_ONLY); 2886 status_t err = file.InitCheck(); 2887 if (err != B_OK) 2888 return err; 2889 2890 char mimeType[256]; 2891 BNodeInfo fileInfo(&file); 2892 fileInfo.GetType(mimeType); 2893 2894 if (strcmp(mimeType, B_PARTIAL_MAIL_TYPE) == 0) { 2895 BMessenger listener(this); 2896 status_t status = BMailDaemon().FetchBody(*ref, &listener); 2897 if (status != B_OK) 2898 fprintf(stderr, "Could not fetch body: %s\n", strerror(status)); 2899 fileInfo.GetType(mimeType); 2900 _SetDownloading(true); 2901 } else 2902 _SetDownloading(false); 2903 2904 // Check if it's a draft file, which contains only the text, and has the 2905 // from, to, bcc, attachments listed as attributes. 2906 if (strcmp(kDraftType, mimeType) == 0) { 2907 BNode node(fRef); 2908 off_t size; 2909 BString string; 2910 2911 fMail = new BEmailMessage; // Not really used much, but still needed. 2912 2913 // Load the raw UTF-8 text from the file. 2914 file.GetSize(&size); 2915 fContentView->TextView()->SetText(&file, 0, size); 2916 2917 // Restore Fields from attributes 2918 if (node.ReadAttrString(B_MAIL_ATTR_TO, &string) == B_OK) 2919 fHeaderView->SetTo(string); 2920 if (node.ReadAttrString(B_MAIL_ATTR_SUBJECT, &string) == B_OK) 2921 fHeaderView->SetSubject(string); 2922 if (node.ReadAttrString(B_MAIL_ATTR_CC, &string) == B_OK) 2923 fHeaderView->SetCc(string); 2924 if (node.ReadAttrString(B_MAIL_ATTR_BCC, &string) == B_OK) 2925 fHeaderView->SetBcc(string); 2926 2927 // Restore account 2928 if (node.ReadAttrString(B_MAIL_ATTR_ACCOUNT, &string) == B_OK) 2929 fHeaderView->SetAccount(string); 2930 2931 // Restore encoding 2932 if (node.ReadAttrString("MAIL:encoding", &string) == B_OK) { 2933 BMenuItem* encodingItem = fEncodingMenu->FindItem(string.String()); 2934 if (encodingItem != NULL) 2935 encodingItem->SetMarked(true); 2936 } 2937 2938 // Restore attachments 2939 if (node.ReadAttrString("MAIL:attachments", &string) == B_OK) { 2940 BMessage msg(REFS_RECEIVED); 2941 entry_ref enc_ref; 2942 2943 BStringList list; 2944 string.Split(":", false, list); 2945 for (int32 i = 0; i < list.CountStrings(); i++) { 2946 BEntry entry(list.StringAt(i), true); 2947 if (entry.Exists()) { 2948 entry.GetRef(&enc_ref); 2949 msg.AddRef("refs", &enc_ref); 2950 } 2951 } 2952 AddEnclosure(&msg); 2953 } 2954 2955 // restore the reading position if available 2956 PostMessage(M_READ_POS); 2957 2958 PostMessage(RESET_BUTTONS); 2959 fIncoming = false; 2960 fDraft = true; 2961 } else { 2962 // A real mail message, parse its headers to get from, to, etc. 2963 fMail = new BEmailMessage(fRef, characterSetForDecoding); 2964 fIncoming = true; 2965 fHeaderView->SetFromMessage(fMail); 2966 } 2967 2968 err = fMail->InitCheck(); 2969 if (err < B_OK) { 2970 delete fMail; 2971 fMail = NULL; 2972 return err; 2973 } 2974 2975 SetTitleForMessage(); 2976 2977 if (fIncoming) { 2978 // Put the addresses in the 'Save Address' Menu 2979 BMenuItem* item; 2980 while ((item = fSaveAddrMenu->RemoveItem((int32)0)) != NULL) 2981 delete item; 2982 2983 // create the list of addresses + names 2984 2985 BList addressList; 2986 get_address_list(addressList, fMail->To(), extract_address); 2987 get_address_list(addressList, fMail->CC(), extract_address); 2988 get_address_list(addressList, fMail->From(), extract_address); 2989 get_address_list(addressList, fMail->ReplyTo(), extract_address); 2990 2991 BList nameList; 2992 get_address_list(nameList, fMail->To(), extract_address_name); 2993 get_address_list(nameList, fMail->CC(), extract_address_name); 2994 get_address_list(nameList, fMail->From(), extract_address_name); 2995 get_address_list(nameList, fMail->ReplyTo(), extract_address_name); 2996 2997 BMessage* msg; 2998 2999 for (int32 i = addressList.CountItems(); i-- > 0;) { 3000 char* address = (char*)addressList.RemoveItem((int32)0); 3001 char* name = (char*)nameList.RemoveItem((int32)0); 3002 3003 // insert the new address in alphabetical order 3004 int32 index = 0; 3005 while ((item = fSaveAddrMenu->ItemAt(index)) != NULL) { 3006 if (!strcmp(address, item->Label())) { 3007 // item already in list 3008 goto skip; 3009 } 3010 3011 if (strcmp(address, item->Label()) < 0) 3012 break; 3013 3014 index++; 3015 } 3016 3017 msg = new BMessage(M_SAVE); 3018 msg->AddString("address", address); 3019 msg->AddString("name", name); 3020 fSaveAddrMenu->AddItem(new BMenuItem(address, msg), index); 3021 3022 skip: 3023 free(address); 3024 free(name); 3025 } 3026 3027 // Clear out existing contents of text view. 3028 fContentView->TextView()->SetText("", (int32)0); 3029 3030 fContentView->TextView()->LoadMessage(fMail, false, NULL); 3031 3032 if (fApp->ShowToolBar()) 3033 _UpdateReadButton(); 3034 } 3035 3036 return B_OK; 3037 } 3038 3039 3040 TMailWindow* 3041 TMailWindow::FrontmostWindow() 3042 { 3043 BAutolock locker(sWindowListLock); 3044 if (sWindowList.CountItems() > 0) 3045 return (TMailWindow*)sWindowList.ItemAt(0); 3046 3047 return NULL; 3048 } 3049 3050 3051 // #pragma mark - 3052 3053 3054 status_t 3055 TMailWindow::_GetQueryPath(BPath* queryPath) const 3056 { 3057 // get the user home directory and from there the query folder 3058 status_t ret = find_directory(B_USER_DIRECTORY, queryPath); 3059 if (ret == B_OK) 3060 ret = queryPath->Append(kQueriesDirectory); 3061 3062 return ret; 3063 } 3064 3065 3066 void 3067 TMailWindow::_RebuildQueryMenu(bool firstTime) 3068 { 3069 while (fQueryMenu->ItemAt(0)) { 3070 BMenuItem* item = fQueryMenu->RemoveItem((int32)0); 3071 delete item; 3072 } 3073 3074 fQueryMenu->AddItem(new BMenuItem(kSameRecipientItem, 3075 new BMessage(M_QUERY_RECIPIENT))); 3076 fQueryMenu->AddItem(new BMenuItem(kSameSenderItem, 3077 new BMessage(M_QUERY_SENDER))); 3078 fQueryMenu->AddItem(new BMenuItem(kSameSubjectItem, 3079 new BMessage(M_QUERY_SUBJECT))); 3080 3081 BPath queryPath; 3082 if (_GetQueryPath(&queryPath) < B_OK) 3083 return; 3084 3085 BDirectory queryDir(queryPath.Path()); 3086 3087 if (firstTime) { 3088 BPrivate::BPathMonitor::StartWatching(queryPath.Path(), 3089 B_WATCH_RECURSIVELY, BMessenger(this, this)); 3090 } 3091 3092 // If we find the named query, add it to the menu. 3093 BEntry entry; 3094 while (queryDir.GetNextEntry(&entry) == B_OK) { 3095 char name[B_FILE_NAME_LENGTH + 1]; 3096 entry.GetName(name); 3097 3098 char* queryString = _BuildQueryString(&entry); 3099 if (queryString == NULL) 3100 continue; 3101 3102 QueryMenu* queryMenu = new QueryMenu(name, false); 3103 queryMenu->SetTargetForItems(be_app); 3104 queryMenu->SetPredicate(queryString); 3105 fQueryMenu->AddItem(queryMenu); 3106 3107 free(queryString); 3108 } 3109 3110 fQueryMenu->AddItem(new BSeparatorItem()); 3111 3112 fQueryMenu->AddItem(new BMenuItem(B_TRANSLATE("Edit queries" 3113 B_UTF8_ELLIPSIS), 3114 new BMessage(M_EDIT_QUERIES), 'E', B_SHIFT_KEY)); 3115 } 3116 3117 3118 char* 3119 TMailWindow::_BuildQueryString(BEntry* entry) const 3120 { 3121 BNode node(entry); 3122 if (node.InitCheck() != B_OK) 3123 return NULL; 3124 3125 uint32 mode; 3126 if (node.ReadAttr(kAttrQueryInitialMode, B_INT32_TYPE, 0, (int32*)&mode, 3127 sizeof(int32)) <= 0) { 3128 mode = kByNameItem; 3129 } 3130 3131 BString queryString; 3132 switch (mode) { 3133 case kByForumlaItem: 3134 { 3135 BString buffer; 3136 if (node.ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK) 3137 queryString << buffer; 3138 break; 3139 } 3140 3141 case kByNameItem: 3142 { 3143 BString buffer; 3144 if (node.ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK) 3145 queryString << "(name==*" << buffer << "*)"; 3146 break; 3147 } 3148 3149 case kByAttributeItem: 3150 { 3151 int32 count = 1; 3152 if (node.ReadAttr(kAttrQueryInitialNumAttrs, B_INT32_TYPE, 0, 3153 (int32*)&count, sizeof(int32)) <= 0) { 3154 count = 1; 3155 } 3156 3157 attr_info info; 3158 if (node.GetAttrInfo(kAttrQueryInitialAttrs, &info) != B_OK) 3159 break; 3160 3161 if (count > 1) 3162 queryString << "("; 3163 3164 char* buffer = new char[info.size]; 3165 if (node.ReadAttr(kAttrQueryInitialAttrs, B_MESSAGE_TYPE, 0, 3166 buffer, (size_t)info.size) == info.size) { 3167 BMessage message; 3168 if (message.Unflatten(buffer) == B_OK) { 3169 for (int32 index = 0; /*index < count*/; index++) { 3170 const char* field; 3171 const char* value; 3172 if (message.FindString("menuSelection", index, &field) 3173 != B_OK 3174 || message.FindString("attrViewText", index, &value) 3175 != B_OK) { 3176 break; 3177 } 3178 3179 // ignore the mime type, we'll force it to be email 3180 // later 3181 if (strcmp(field, "BEOS:TYPE") != 0) { 3182 // TODO: check if subMenu contains the type of 3183 // comparison we are suppose to make here 3184 queryString << "(" << field << "==\"" 3185 << value << "\")"; 3186 3187 int32 logicMenuSelectedIndex; 3188 if (message.FindInt32("logicalRelation", index, 3189 &logicMenuSelectedIndex) == B_OK) { 3190 if (logicMenuSelectedIndex == 0) 3191 queryString << "&&"; 3192 else if (logicMenuSelectedIndex == 1) 3193 queryString << "||"; 3194 } else 3195 break; 3196 } 3197 } 3198 } 3199 } 3200 3201 if (count > 1) 3202 queryString << ")"; 3203 3204 delete [] buffer; 3205 break; 3206 } 3207 3208 default: 3209 break; 3210 } 3211 3212 if (queryString.Length() == 0) 3213 return NULL; 3214 3215 // force it to check for email only 3216 if (queryString.FindFirst("text/x-email") < 0) { 3217 BString temp; 3218 temp << "(" << queryString << "&&(BEOS:TYPE==\"text/x-email\"))"; 3219 queryString = temp; 3220 } 3221 3222 return strdup(queryString.String()); 3223 } 3224 3225 3226 void 3227 TMailWindow::_LaunchQuery(const char* title, const char* attribute, 3228 BString text) 3229 { 3230 /* ToDo: 3231 If the search attribute is To or From, it'd be nice to parse the 3232 search text to separate the email address and user name. 3233 Then search for 'name || address' to get all mails of people, 3234 never mind the account or mail config they sent from. 3235 */ 3236 text.ReplaceAll(" ", "*"); // query on MAIL:track demands * for space 3237 text.ReplaceAll("\"", "\\\""); 3238 3239 BString* term = new BString("(("); 3240 term->Append(attribute); 3241 term->Append("==\"*"); 3242 term->Append(text); 3243 term->Append("*\")&&(BEOS:TYPE==\"text/x-email\"))"); 3244 3245 BPath queryPath; 3246 if (find_directory(B_USER_CACHE_DIRECTORY, &queryPath) != B_OK) 3247 return; 3248 queryPath.Append("Mail"); 3249 if ((create_directory(queryPath.Path(), 0777)) != B_OK) 3250 return; 3251 queryPath.Append(title); 3252 BFile query(queryPath.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 3253 if (query.InitCheck() != B_OK) 3254 return; 3255 3256 BNode queryNode(queryPath.Path()); 3257 if (queryNode.InitCheck() != B_OK) 3258 return; 3259 3260 // Copy layout from DefaultQueryTemplates 3261 BPath templatePath; 3262 find_directory(B_USER_SETTINGS_DIRECTORY, &templatePath); 3263 templatePath.Append("Tracker/DefaultQueryTemplates/text_x-email"); 3264 BNode templateNode(templatePath.Path()); 3265 3266 if (templateNode.InitCheck() == B_OK) { 3267 if (CopyAttributes(templateNode, queryNode) != B_OK) { 3268 syslog(LOG_INFO, "Mail: copying x-email DefaultQueryTemplate " 3269 "attributes failed"); 3270 } 3271 } 3272 3273 queryNode.WriteAttrString("_trk/qrystr", term); 3274 BNodeInfo nodeInfo(&queryNode); 3275 nodeInfo.SetType("application/x-vnd.Be-query"); 3276 3277 // Launch query 3278 BEntry entry(queryPath.Path()); 3279 entry_ref ref; 3280 if (entry.GetRef(&ref) == B_OK) 3281 be_roster->Launch(&ref); 3282 } 3283 3284 3285 void 3286 TMailWindow::_CreateNewPerson(BString address, BString name) 3287 { 3288 BMessage message(M_LAUNCH_PEOPLE); 3289 message.AddString("META:name", name); 3290 message.AddString("META:email", address); 3291 3292 status_t result = be_roster->Launch("application/x-person", &message); 3293 3294 if ((result != B_OK) && (result != B_ALREADY_RUNNING)) { 3295 BAlert* alert = new BAlert("", B_TRANSLATE( 3296 "Sorry, could not find an application that " 3297 "supports the 'Person' data type."), 3298 B_TRANSLATE("OK")); 3299 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3300 alert->Go(); 3301 } 3302 } 3303 3304 3305 void 3306 TMailWindow::_AddReadButton() 3307 { 3308 BNode node(fRef); 3309 3310 read_flags flag = B_UNREAD; 3311 read_read_attr(node, flag); 3312 3313 if (flag == B_READ) { 3314 fToolBar->SetActionVisible(M_UNREAD, true); 3315 fToolBar->SetActionVisible(M_READ, false); 3316 } else { 3317 fToolBar->SetActionVisible(M_UNREAD, false); 3318 fToolBar->SetActionVisible(M_READ, true); 3319 } 3320 } 3321 3322 3323 void 3324 TMailWindow::_UpdateReadButton() 3325 { 3326 if (fApp->ShowToolBar()) { 3327 if (!fAutoMarkRead && fIncoming) 3328 _AddReadButton(); 3329 } 3330 UpdateViews(); 3331 } 3332 3333 3334 void 3335 TMailWindow::_UpdateLabel(uint32 command, const char* label, bool show) 3336 { 3337 BButton* button = fToolBar->FindButton(command); 3338 if (button != NULL) { 3339 button->SetLabel(show ? label : NULL); 3340 button->SetToolTip(show ? NULL : label); 3341 } 3342 } 3343 3344 3345 void 3346 TMailWindow::_SetDownloading(bool downloading) 3347 { 3348 fDownloading = downloading; 3349 } 3350 3351 3352 uint32 3353 TMailWindow::_CurrentCharacterSet() const 3354 { 3355 uint32 defaultCharSet = fResending || !fIncoming 3356 ? fApp->MailCharacterSet() : B_MAIL_NULL_CONVERSION; 3357 3358 BMenuItem* marked = fEncodingMenu->FindMarked(); 3359 if (marked == NULL) 3360 return defaultCharSet; 3361 3362 return marked->Message()->GetInt32("charset", defaultCharSet); 3363 } 3364