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