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