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 enclosure" B_UTF8_ELLIPSIS), 457 new BMessage(M_ADD), 'E')); 458 menu->AddItem(fRemove = new BMenuItem( 459 B_TRANSLATE("Remove enclosure"), 460 new BMessage(M_REMOVE), 'T')); 461 } 462 } 463 if (fIncoming) { 464 menu->AddSeparatorItem(); 465 fSaveAddrMenu = new BMenu(B_TRANSLATE("Save address")); 466 menu->AddItem(fSaveAddrMenu); 467 } 468 469 // Encoding menu 470 471 fEncodingMenu = new BMenu(B_TRANSLATE("Encoding")); 472 473 BMenuItem* automaticItem = NULL; 474 if (!resending && fIncoming) { 475 // Reading a message, display the Automatic item 476 msg = new BMessage(CHARSET_CHOICE_MADE); 477 msg->AddInt32("charset", B_MAIL_NULL_CONVERSION); 478 automaticItem = new BMenuItem(B_TRANSLATE("Automatic"), msg); 479 fEncodingMenu->AddItem(automaticItem); 480 fEncodingMenu->AddSeparatorItem(); 481 } 482 483 uint32 defaultCharSet = resending || !fIncoming 484 ? fApp->MailCharacterSet() : B_MAIL_NULL_CONVERSION; 485 bool markedCharSet = false; 486 487 BCharacterSetRoster roster; 488 BCharacterSet charSet; 489 while (roster.GetNextCharacterSet(&charSet) == B_OK) { 490 BString name(charSet.GetPrintName()); 491 const char* mime = charSet.GetMIMEName(); 492 if (mime != NULL) 493 name << " (" << mime << ")"; 494 495 uint32 convertID; 496 if (mime == NULL || strcasecmp(mime, "UTF-8") != 0) 497 convertID = charSet.GetConversionID(); 498 else 499 convertID = B_MAIL_UTF8_CONVERSION; 500 501 msg = new BMessage(CHARSET_CHOICE_MADE); 502 msg->AddInt32("charset", convertID); 503 fEncodingMenu->AddItem(item = new BMenuItem(name.String(), msg)); 504 if (convertID == defaultCharSet && !markedCharSet) { 505 item->SetMarked(true); 506 markedCharSet = true; 507 } 508 } 509 510 msg = new BMessage(CHARSET_CHOICE_MADE); 511 msg->AddInt32("charset", B_MAIL_US_ASCII_CONVERSION); 512 fEncodingMenu->AddItem(item = new BMenuItem("US-ASCII", msg)); 513 if (defaultCharSet == B_MAIL_US_ASCII_CONVERSION && !markedCharSet) { 514 item->SetMarked(true); 515 markedCharSet = true; 516 } 517 518 if (automaticItem != NULL && !markedCharSet) 519 automaticItem->SetMarked(true); 520 521 menu->AddSeparatorItem(); 522 menu->AddItem(fEncodingMenu); 523 fMenuBar->AddItem(menu); 524 fEncodingMenu->SetRadioMode(true); 525 fEncodingMenu->SetTargetForItems(this); 526 527 // Spam Menu 528 529 if (!resending && fIncoming && fApp->ShowSpamGUI()) { 530 menu = new BMenu("Spam filtering"); 531 menu->AddItem(new BMenuItem("Mark as spam and move to trash", 532 new BMessage(M_TRAIN_SPAM_AND_DELETE), 'K')); 533 menu->AddItem(new BMenuItem("Mark as spam", 534 new BMessage(M_TRAIN_SPAM), 'K', B_OPTION_KEY)); 535 menu->AddSeparatorItem(); 536 menu->AddItem(new BMenuItem("Unmark this message", 537 new BMessage(M_UNTRAIN))); 538 menu->AddSeparatorItem(); 539 menu->AddItem(new BMenuItem("Mark as genuine", 540 new BMessage(M_TRAIN_GENUINE), 'K', B_SHIFT_KEY)); 541 fMenuBar->AddItem(menu); 542 } 543 544 // Queries Menu 545 546 fQueryMenu = new BMenu(B_TRANSLATE("Queries")); 547 fMenuBar->AddItem(fQueryMenu); 548 549 _RebuildQueryMenu(true); 550 551 // Button Bar 552 553 BuildToolBar(); 554 555 if (!fApp->ShowToolBar()) 556 fToolBar->Hide(); 557 558 fHeaderView = new THeaderView(fIncoming, resending, 559 fApp->DefaultAccount()); 560 561 fContentView = new TContentView(fIncoming, const_cast<BFont*>(font), 562 false, fApp->ColoredQuotes()); 563 // TContentView needs to be properly const, for now cast away constness 564 565 BLayoutBuilder::Group<>(this, B_VERTICAL, 0) 566 .Add(fMenuBar) 567 .Add(fToolBar) 568 .AddGroup(B_VERTICAL, 0) 569 .Add(fHeaderView) 570 .SetInsets(B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING) 571 .End() 572 .Add(fContentView); 573 574 if (to != NULL) 575 fHeaderView->SetTo(to); 576 577 AddShortcut('n', B_COMMAND_KEY, new BMessage(M_NEW)); 578 579 // If auto-signature, add signature to the text here. 580 581 BString signature = fApp->Signature(); 582 583 if (!fIncoming && strcmp(signature.String(), B_TRANSLATE("None")) != 0) { 584 if (strcmp(signature.String(), B_TRANSLATE("Random")) == 0) 585 PostMessage(M_RANDOM_SIG); 586 else { 587 // Create a query to find this signature 588 BVolume volume; 589 BVolumeRoster().GetBootVolume(&volume); 590 591 BQuery query; 592 query.SetVolume(&volume); 593 query.PushAttr(INDEX_SIGNATURE); 594 query.PushString(signature.String()); 595 query.PushOp(B_EQ); 596 query.Fetch(); 597 598 // If we find the named query, add it to the text. 599 BEntry entry; 600 if (query.GetNextEntry(&entry) == B_NO_ERROR) { 601 BFile file; 602 file.SetTo(&entry, O_RDWR); 603 if (file.InitCheck() == B_NO_ERROR) { 604 entry_ref ref; 605 entry.GetRef(&ref); 606 607 BMessage msg(M_SIGNATURE); 608 msg.AddRef("ref", &ref); 609 PostMessage(&msg); 610 } 611 } else { 612 char tempString [2048]; 613 query.GetPredicate (tempString, sizeof (tempString)); 614 printf ("Query failed, was looking for: %s\n", tempString); 615 } 616 } 617 } 618 619 OpenMessage(ref, _CurrentCharacterSet()); 620 621 AddShortcut('q', B_SHIFT_KEY, new BMessage(kMsgQuitAndKeepAllStatus)); 622 } 623 624 625 BBitmap* 626 TMailWindow::_RetrieveVectorIcon(int32 id) 627 { 628 // Lock access to the list 629 BAutolock lock(sBitmapCacheLock); 630 if (!lock.IsLocked()) 631 return NULL; 632 633 // Check for the bitmap in the cache first 634 BitmapItem* item; 635 for (int32 i = 0; (item = sBitmapCache.ItemAt(i)) != NULL; i++) { 636 if (item->id == id) 637 return item->bm; 638 } 639 640 // If it's not in the cache, try to load it 641 BResources* res = BApplication::AppResources(); 642 if (res == NULL) 643 return NULL; 644 size_t size; 645 const void* data = res->LoadResource(B_VECTOR_ICON_TYPE, id, &size); 646 647 if (!data) 648 return NULL; 649 650 BBitmap* bitmap = new BBitmap(BRect(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 REFS_RECEIVED: 1584 AddEnclosure(msg); 1585 break; 1586 1587 // 1588 // Navigation Messages 1589 // 1590 case M_UNREAD: 1591 MarkMessageRead(fRef, B_SEEN); 1592 _UpdateReadButton(); 1593 PostMessage(M_NEXTMSG); 1594 break; 1595 case M_READ: 1596 wasReadMsg = true; 1597 _UpdateReadButton(); 1598 msg->what = M_NEXTMSG; 1599 case M_PREVMSG: 1600 case M_NEXTMSG: 1601 { 1602 if (fRef == NULL) 1603 break; 1604 entry_ref orgRef = *fRef; 1605 entry_ref nextRef = *fRef; 1606 if (GetTrackerWindowFile(&nextRef, (msg->what == M_NEXTMSG))) { 1607 TMailWindow* window = static_cast<TMailApp*>(be_app) 1608 ->FindWindow(nextRef); 1609 if (window == NULL) { 1610 BNode node(fRef); 1611 read_flags currentFlag; 1612 if (read_read_attr(node, currentFlag) != B_OK) 1613 currentFlag = B_UNREAD; 1614 if (fAutoMarkRead == true) 1615 MarkMessageRead(fRef, B_READ); 1616 else if (currentFlag != B_READ && !wasReadMsg) 1617 MarkMessageRead(fRef, B_SEEN); 1618 1619 OpenMessage(&nextRef, _CurrentCharacterSet()); 1620 } else { 1621 window->Activate(); 1622 //fSent = true; 1623 PostMessage(B_CLOSE_REQUESTED); 1624 } 1625 1626 SetTrackerSelectionToCurrent(); 1627 } else { 1628 if (wasReadMsg) 1629 PostMessage(B_CLOSE_REQUESTED); 1630 1631 beep(); 1632 } 1633 if (wasReadMsg) 1634 MarkMessageRead(&orgRef, B_READ); 1635 break; 1636 } 1637 1638 case M_SAVE_POSITION: 1639 if (fRef != NULL) 1640 SaveTrackerPosition(fRef); 1641 break; 1642 1643 case RESET_BUTTONS: 1644 fChanged = false; 1645 fFieldState = 0; 1646 if (!fHeaderView->IsToEmpty()) 1647 fFieldState |= FIELD_TO; 1648 if (!fHeaderView->IsSubjectEmpty()) 1649 fFieldState |= FIELD_SUBJECT; 1650 if (!fHeaderView->IsCcEmpty()) 1651 fFieldState |= FIELD_CC; 1652 if (!fHeaderView->IsBccEmpty()) 1653 fFieldState |= FIELD_BCC; 1654 if (fContentView->TextView()->TextLength() != 0) 1655 fFieldState |= FIELD_BODY; 1656 1657 fToolBar->SetActionEnabled(M_SAVE_AS_DRAFT, false); 1658 fToolBar->SetActionEnabled(M_PRINT, fFieldState); 1659 fToolBar->SetActionEnabled(M_SEND_NOW, (fFieldState & FIELD_TO) 1660 || (fFieldState & FIELD_BCC)); 1661 break; 1662 1663 case M_CHECK_SPELLING: 1664 if (gDictCount == 0) 1665 // Give the application time to init and load dictionaries. 1666 snooze (1500000); 1667 if (!gDictCount) { 1668 beep(); 1669 BAlert* alert = new BAlert("", 1670 B_TRANSLATE("Mail couldn't find its dictionary."), 1671 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, 1672 B_OFFSET_SPACING, B_STOP_ALERT); 1673 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1674 alert->Go(); 1675 } else { 1676 fSpelling->SetMarked(!fSpelling->IsMarked()); 1677 fContentView->TextView()->EnableSpellCheck( 1678 fSpelling->IsMarked()); 1679 } 1680 break; 1681 1682 case M_QUERY_RECIPIENT: 1683 { 1684 BString searchText(fHeaderView->To()); 1685 if (searchText != "") { 1686 _LaunchQuery(kSameRecipientItem, B_MAIL_ATTR_TO, 1687 searchText); 1688 } 1689 break; 1690 } 1691 1692 case M_QUERY_SENDER: 1693 { 1694 BString searchText(fHeaderView->From()); 1695 if (searchText != "") { 1696 _LaunchQuery(kSameSenderItem, B_MAIL_ATTR_FROM, 1697 searchText); 1698 } 1699 break; 1700 } 1701 1702 case M_QUERY_SUBJECT: 1703 { 1704 // If there's no thread attribute (e.g. new mail) use subject 1705 BString searchText(fHeaderView->Subject()); 1706 BNode node(fRef); 1707 if (node.InitCheck() == B_OK) 1708 node.ReadAttrString(B_MAIL_ATTR_THREAD, &searchText); 1709 1710 if (searchText != "") { 1711 // query for subject as sent mails have no thread attribute 1712 _LaunchQuery(kSameSubjectItem, B_MAIL_ATTR_SUBJECT, 1713 searchText); 1714 } 1715 break; 1716 } 1717 case M_EDIT_QUERIES: 1718 { 1719 BPath path; 1720 if (_GetQueryPath(&path) < B_OK) 1721 break; 1722 1723 // the user used this command, make sure the folder actually 1724 // exists - if it didn't inform the user what to do with it 1725 BEntry entry(path.Path()); 1726 bool showAlert = false; 1727 if (!entry.Exists()) { 1728 showAlert = true; 1729 create_directory(path.Path(), 0777); 1730 } 1731 1732 BEntry folderEntry; 1733 if (folderEntry.SetTo(path.Path()) == B_OK 1734 && folderEntry.Exists()) { 1735 BMessage openFolderCommand(B_REFS_RECEIVED); 1736 BMessenger tracker("application/x-vnd.Be-TRAK"); 1737 1738 entry_ref ref; 1739 folderEntry.GetRef(&ref); 1740 openFolderCommand.AddRef("refs", &ref); 1741 tracker.SendMessage(&openFolderCommand); 1742 } 1743 1744 if (showAlert) { 1745 // just some patience before Tracker pops up the folder 1746 snooze(250000); 1747 BAlert* alert = new BAlert(B_TRANSLATE("helpful message"), 1748 B_TRANSLATE("Put your favorite e-mail queries and query " 1749 "templates in this folder."), B_TRANSLATE("OK"), NULL, NULL, 1750 B_WIDTH_AS_USUAL, B_IDEA_ALERT); 1751 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1752 alert->Go(NULL); 1753 } 1754 1755 break; 1756 } 1757 1758 case B_PATH_MONITOR: 1759 _RebuildQueryMenu(); 1760 break; 1761 1762 default: 1763 BWindow::MessageReceived(msg); 1764 } 1765 } 1766 1767 1768 void 1769 TMailWindow::AddEnclosure(BMessage* msg) 1770 { 1771 if (fEnclosuresView == NULL && !fIncoming) { 1772 fEnclosuresView = new TEnclosuresView; 1773 AddChild(fEnclosuresView, fContentView); 1774 } 1775 1776 if (fEnclosuresView == NULL) 1777 return; 1778 1779 if (msg && msg->HasRef("refs")) { 1780 // Add enclosure to view 1781 PostMessage(msg, fEnclosuresView); 1782 1783 fChanged = true; 1784 BEntry entry; 1785 entry_ref ref; 1786 msg->FindRef("refs", &ref); 1787 entry.SetTo(&ref); 1788 entry.GetParent(&entry); 1789 entry.GetRef(&fOpenFolder); 1790 } 1791 } 1792 1793 1794 bool 1795 TMailWindow::QuitRequested() 1796 { 1797 int32 result; 1798 1799 if ((!fIncoming || (fIncoming && fResending)) && fChanged && !fSent 1800 && (!fHeaderView->IsToEmpty() 1801 || !fHeaderView->IsSubjectEmpty() 1802 || !fHeaderView->IsCcEmpty() 1803 || !fHeaderView->IsBccEmpty() 1804 || (fContentView->TextView() != NULL 1805 && strlen(fContentView->TextView()->Text())) 1806 || (fEnclosuresView != NULL 1807 && fEnclosuresView->fList->CountItems()))) { 1808 if (fResending) { 1809 BAlert* alert = new BAlert("", B_TRANSLATE( 1810 "Send this message before closing?"), 1811 B_TRANSLATE("Cancel"), 1812 B_TRANSLATE("Don't send"), 1813 B_TRANSLATE("Send"), 1814 B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT); 1815 alert->SetShortcut(0, B_ESCAPE); 1816 alert->SetShortcut(1, 'd'); 1817 alert->SetShortcut(2, 's'); 1818 result = alert->Go(); 1819 1820 switch (result) { 1821 case 0: // Cancel 1822 return false; 1823 case 1: // Don't send 1824 break; 1825 case 2: // Send 1826 Send(true); 1827 break; 1828 } 1829 } else { 1830 BAlert* alert = new BAlert("", 1831 B_TRANSLATE("Save this message as a draft before closing?"), 1832 B_TRANSLATE("Cancel"), 1833 B_TRANSLATE("Don't save"), 1834 B_TRANSLATE("Save"), 1835 B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT); 1836 alert->SetShortcut(0, B_ESCAPE); 1837 alert->SetShortcut(1, 'd'); 1838 alert->SetShortcut(2, 's'); 1839 result = alert->Go(); 1840 switch (result) { 1841 case 0: // Cancel 1842 return false; 1843 case 1: // Don't Save 1844 break; 1845 case 2: // Save 1846 Send(false); 1847 break; 1848 } 1849 } 1850 } 1851 1852 BMessage message(WINDOW_CLOSED); 1853 message.AddInt32("kind", MAIL_WINDOW); 1854 message.AddPointer("window", this); 1855 be_app->PostMessage(&message); 1856 1857 if (CurrentMessage() && CurrentMessage()->HasString("status")) { 1858 // User explicitly requests a status to set this message to. 1859 if (!CurrentMessage()->HasString("same")) { 1860 const char* status = CurrentMessage()->FindString("status"); 1861 if (status != NULL) { 1862 BNode node(fRef); 1863 if (node.InitCheck() == B_NO_ERROR) { 1864 node.RemoveAttr(B_MAIL_ATTR_STATUS); 1865 WriteAttrString(&node, B_MAIL_ATTR_STATUS, status); 1866 } 1867 } 1868 } 1869 } else if (fRef != NULL && !fKeepStatusOnQuit) { 1870 // ...Otherwise just set the message read 1871 if (fAutoMarkRead == true) 1872 MarkMessageRead(fRef, B_READ); 1873 else { 1874 BNode node(fRef); 1875 read_flags currentFlag; 1876 if (read_read_attr(node, currentFlag) != B_OK) 1877 currentFlag = B_UNREAD; 1878 if (currentFlag == B_UNREAD) 1879 MarkMessageRead(fRef, B_SEEN); 1880 } 1881 } 1882 1883 BPrivate::BPathMonitor::StopWatching(BMessenger(this, this)); 1884 1885 return true; 1886 } 1887 1888 1889 void 1890 TMailWindow::Show() 1891 { 1892 if (Lock()) { 1893 if (!fResending && (fIncoming || fReplying)) { 1894 fContentView->TextView()->MakeFocus(true); 1895 } else { 1896 fHeaderView->ToControl()->MakeFocus(true); 1897 fHeaderView->ToControl()->SelectAll(); 1898 } 1899 Unlock(); 1900 } 1901 BWindow::Show(); 1902 } 1903 1904 1905 void 1906 TMailWindow::Zoom(BPoint /*pos*/, float /*x*/, float /*y*/) 1907 { 1908 float height; 1909 float width; 1910 1911 BRect rect = Frame(); 1912 width = 80 * fApp->ContentFont().StringWidth("M") 1913 + (rect.Width() - fContentView->TextView()->Bounds().Width() + 6); 1914 1915 BScreen screen(this); 1916 BRect screenFrame = screen.Frame(); 1917 if (width > (screenFrame.Width() - 8)) 1918 width = screenFrame.Width() - 8; 1919 1920 height = max_c(fContentView->TextView()->CountLines(), 20) 1921 * fContentView->TextView()->LineHeight(0) 1922 + (rect.Height() - fContentView->TextView()->Bounds().Height()); 1923 if (height > (screenFrame.Height() - 29)) 1924 height = screenFrame.Height() - 29; 1925 1926 rect.right = rect.left + width; 1927 rect.bottom = rect.top + height; 1928 1929 if (abs((int)(Frame().Width() - rect.Width())) < 5 1930 && abs((int)(Frame().Height() - rect.Height())) < 5) { 1931 rect = fZoom; 1932 } else { 1933 fZoom = Frame(); 1934 screenFrame.InsetBy(6, 6); 1935 1936 if (rect.Width() > screenFrame.Width()) 1937 rect.right = rect.left + screenFrame.Width(); 1938 if (rect.Height() > screenFrame.Height()) 1939 rect.bottom = rect.top + screenFrame.Height(); 1940 1941 if (rect.right > screenFrame.right) { 1942 rect.left -= rect.right - screenFrame.right; 1943 rect.right = screenFrame.right; 1944 } 1945 if (rect.bottom > screenFrame.bottom) { 1946 rect.top -= rect.bottom - screenFrame.bottom; 1947 rect.bottom = screenFrame.bottom; 1948 } 1949 if (rect.left < screenFrame.left) { 1950 rect.right += screenFrame.left - rect.left; 1951 rect.left = screenFrame.left; 1952 } 1953 if (rect.top < screenFrame.top) { 1954 rect.bottom += screenFrame.top - rect.top; 1955 rect.top = screenFrame.top; 1956 } 1957 } 1958 1959 ResizeTo(rect.Width(), rect.Height()); 1960 MoveTo(rect.LeftTop()); 1961 } 1962 1963 1964 void 1965 TMailWindow::WindowActivated(bool status) 1966 { 1967 if (status) { 1968 BAutolock locker(sWindowListLock); 1969 sWindowList.RemoveItem(this); 1970 sWindowList.AddItem(this, 0); 1971 } 1972 } 1973 1974 1975 void 1976 TMailWindow::Forward(entry_ref* ref, TMailWindow* window, 1977 bool includeAttachments) 1978 { 1979 BEmailMessage* mail = window->Mail(); 1980 if (mail == NULL) 1981 return; 1982 1983 uint32 useAccountFrom = fApp->UseAccountFrom(); 1984 1985 fMail = mail->ForwardMessage(useAccountFrom == ACCOUNT_FROM_MAIL, 1986 includeAttachments); 1987 1988 BFile file(ref, O_RDONLY); 1989 if (file.InitCheck() < B_NO_ERROR) 1990 return; 1991 1992 fHeaderView->SetSubject(fMail->Subject()); 1993 1994 // set mail account 1995 1996 if (useAccountFrom == ACCOUNT_FROM_MAIL) 1997 fHeaderView->SetAccount(fMail->Account()); 1998 1999 if (fMail->CountComponents() > 1) { 2000 // if there are any enclosures to be added, first add the enclosures 2001 // view to the window 2002 AddEnclosure(NULL); 2003 if (fEnclosuresView) 2004 fEnclosuresView->AddEnclosuresFromMail(fMail); 2005 } 2006 2007 fContentView->TextView()->LoadMessage(fMail, false, NULL); 2008 fChanged = false; 2009 fFieldState = 0; 2010 } 2011 2012 2013 void 2014 TMailWindow::Print() 2015 { 2016 BPrintJob print(Title()); 2017 2018 if (!fApp->HasPrintSettings()) { 2019 if (print.Settings()) { 2020 fApp->SetPrintSettings(print.Settings()); 2021 } else { 2022 PrintSetup(); 2023 if (!fApp->HasPrintSettings()) 2024 return; 2025 } 2026 } 2027 2028 print.SetSettings(new BMessage(fApp->PrintSettings())); 2029 2030 if (print.ConfigJob() == B_OK) { 2031 int32 curPage = 1; 2032 int32 lastLine = 0; 2033 BTextView header_view(print.PrintableRect(), "header", 2034 print.PrintableRect().OffsetByCopy(BPoint( 2035 -print.PrintableRect().left, -print.PrintableRect().top)), 2036 B_FOLLOW_ALL_SIDES); 2037 2038 //---------Init the header fields 2039 #define add_header_field(label, field) { \ 2040 /*header_view.SetFontAndColor(be_bold_font);*/ \ 2041 header_view.Insert(label); \ 2042 header_view.Insert(" "); \ 2043 /*header_view.SetFontAndColor(be_plain_font);*/ \ 2044 header_view.Insert(field); \ 2045 header_view.Insert("\n"); \ 2046 } 2047 2048 add_header_field("Subject:", fHeaderView->Subject()); 2049 add_header_field("To:", fHeaderView->To()); 2050 if (!fHeaderView->IsCcEmpty()) 2051 add_header_field(B_TRANSLATE("Cc:"), fHeaderView->Cc()); 2052 2053 if (!fHeaderView->IsDateEmpty()) 2054 header_view.Insert(fHeaderView->Date()); 2055 2056 int32 maxLine = fContentView->TextView()->CountLines(); 2057 BRect pageRect = print.PrintableRect(); 2058 BRect curPageRect = pageRect; 2059 2060 print.BeginJob(); 2061 float header_height = header_view.TextHeight(0, 2062 header_view.CountLines()); 2063 2064 BRect rect(0, 0, pageRect.Width(), header_height); 2065 BBitmap bmap(rect, B_BITMAP_ACCEPTS_VIEWS, B_RGBA32); 2066 bmap.Lock(); 2067 bmap.AddChild(&header_view); 2068 print.DrawView(&header_view, rect, BPoint(0.0, 0.0)); 2069 HorizontalLine line(BRect(0, 0, pageRect.right, 0)); 2070 bmap.AddChild(&line); 2071 print.DrawView(&line, line.Bounds(), BPoint(0, header_height + 1)); 2072 bmap.Unlock(); 2073 header_height += 5; 2074 2075 do { 2076 int32 lineOffset = fContentView->TextView()->OffsetAt(lastLine); 2077 curPageRect.OffsetTo(0, 2078 fContentView->TextView()->PointAt(lineOffset).y); 2079 2080 int32 fromLine = lastLine; 2081 lastLine = fContentView->TextView()->LineAt( 2082 BPoint(0.0, curPageRect.bottom - ((curPage == 1) 2083 ? header_height : 0))); 2084 2085 float curPageHeight = fContentView->TextView()->TextHeight( 2086 fromLine, lastLine) + (curPage == 1 ? header_height : 0); 2087 2088 if (curPageHeight > pageRect.Height()) { 2089 curPageHeight = fContentView->TextView()->TextHeight( 2090 fromLine, --lastLine) + (curPage == 1 ? header_height : 0); 2091 } 2092 curPageRect.bottom = curPageRect.top + curPageHeight - 1.0; 2093 2094 if (curPage >= print.FirstPage() && curPage <= print.LastPage()) { 2095 print.DrawView(fContentView->TextView(), curPageRect, 2096 BPoint(0.0, curPage == 1 ? header_height : 0.0)); 2097 print.SpoolPage(); 2098 } 2099 2100 curPageRect = pageRect; 2101 lastLine++; 2102 curPage++; 2103 2104 } while (print.CanContinue() && lastLine < maxLine); 2105 2106 print.CommitJob(); 2107 bmap.RemoveChild(&header_view); 2108 bmap.RemoveChild(&line); 2109 } 2110 } 2111 2112 2113 void 2114 TMailWindow::PrintSetup() 2115 { 2116 BPrintJob printJob("mail_print"); 2117 2118 if (fApp->HasPrintSettings()) { 2119 BMessage printSettings = fApp->PrintSettings(); 2120 printJob.SetSettings(new BMessage(printSettings)); 2121 } 2122 2123 if (printJob.ConfigPage() == B_OK) 2124 fApp->SetPrintSettings(printJob.Settings()); 2125 } 2126 2127 2128 void 2129 TMailWindow::SetTo(const char* mailTo, const char* subject, const char* ccTo, 2130 const char* bccTo, const BString* body, BMessage* enclosures) 2131 { 2132 Lock(); 2133 2134 if (mailTo != NULL && mailTo[0]) 2135 fHeaderView->SetTo(mailTo); 2136 if (subject != NULL && subject[0]) 2137 fHeaderView->SetSubject(subject); 2138 if (ccTo != NULL && ccTo[0]) 2139 fHeaderView->SetCc(ccTo); 2140 if (bccTo != NULL && bccTo[0]) 2141 fHeaderView->SetBcc(bccTo); 2142 2143 if (body != NULL && body->Length()) { 2144 fContentView->TextView()->SetText(body->String(), body->Length()); 2145 fContentView->TextView()->GoToLine(0); 2146 } 2147 2148 if (enclosures && enclosures->HasRef("refs")) 2149 AddEnclosure(enclosures); 2150 2151 Unlock(); 2152 } 2153 2154 2155 void 2156 TMailWindow::CopyMessage(entry_ref* ref, TMailWindow* src) 2157 { 2158 BNode file(ref); 2159 if (file.InitCheck() == B_OK) { 2160 BString string; 2161 if (file.ReadAttrString(B_MAIL_ATTR_TO, &string) == B_OK) 2162 fHeaderView->SetTo(string); 2163 2164 if (file.ReadAttrString(B_MAIL_ATTR_SUBJECT, &string) == B_OK) 2165 fHeaderView->SetSubject(string); 2166 2167 if (file.ReadAttrString(B_MAIL_ATTR_CC, &string) == B_OK) 2168 fHeaderView->SetCc(string); 2169 } 2170 2171 TTextView* text = src->fContentView->TextView(); 2172 text_run_array* style = text->RunArray(0, text->TextLength()); 2173 2174 fContentView->TextView()->SetText(text->Text(), text->TextLength(), style); 2175 2176 free(style); 2177 } 2178 2179 2180 void 2181 TMailWindow::Reply(entry_ref* ref, TMailWindow* window, uint32 type) 2182 { 2183 fRepliedMail = *ref; 2184 SetOriginatingWindow(window); 2185 2186 BEmailMessage* mail = window->Mail(); 2187 if (mail == NULL) 2188 return; 2189 2190 if (type == M_REPLY_ALL) 2191 type = B_MAIL_REPLY_TO_ALL; 2192 else if (type == M_REPLY_TO_SENDER) 2193 type = B_MAIL_REPLY_TO_SENDER; 2194 else 2195 type = B_MAIL_REPLY_TO; 2196 2197 uint32 useAccountFrom = fApp->UseAccountFrom(); 2198 2199 fMail = mail->ReplyMessage(mail_reply_to_mode(type), 2200 useAccountFrom == ACCOUNT_FROM_MAIL, QUOTE); 2201 2202 // set header fields 2203 fHeaderView->SetTo(fMail->To()); 2204 fHeaderView->SetCc(fMail->CC()); 2205 fHeaderView->SetSubject(fMail->Subject()); 2206 2207 int32 accountID; 2208 BFile file(window->fRef, B_READ_ONLY); 2209 if (file.ReadAttr("MAIL:reply_with", B_INT32_TYPE, 0, &accountID, 2210 sizeof(int32)) != B_OK) 2211 accountID = -1; 2212 2213 // set mail account 2214 2215 if ((useAccountFrom == ACCOUNT_FROM_MAIL) || (accountID > -1)) { 2216 if (useAccountFrom == ACCOUNT_FROM_MAIL) 2217 fHeaderView->SetAccount(fMail->Account()); 2218 else 2219 fHeaderView->SetAccount(accountID); 2220 } 2221 2222 // create preamble string 2223 2224 BString preamble = fApp->ReplyPreamble(); 2225 2226 BString name; 2227 mail->GetName(&name); 2228 if (name.Length() <= 0) 2229 name = B_TRANSLATE("(Name unavailable)"); 2230 2231 BString address(mail->From()); 2232 if (address.Length() <= 0) 2233 address = B_TRANSLATE("(Address unavailable)"); 2234 2235 BString date(mail->HeaderField("Date")); 2236 if (date.Length() <= 0) 2237 date = B_TRANSLATE("(Date unavailable)"); 2238 2239 preamble.ReplaceAll("%n", name); 2240 preamble.ReplaceAll("%e", address); 2241 preamble.ReplaceAll("%d", date); 2242 preamble.ReplaceAll("\\n", "\n"); 2243 2244 // insert (if selection) or load (if whole mail) message text into text view 2245 2246 int32 finish, start; 2247 window->fContentView->TextView()->GetSelection(&start, &finish); 2248 if (start != finish) { 2249 char* text = (char*)malloc(finish - start + 1); 2250 if (text == NULL) 2251 return; 2252 2253 window->fContentView->TextView()->GetText(start, finish - start, text); 2254 if (text[strlen(text) - 1] != '\n') { 2255 text[strlen(text)] = '\n'; 2256 finish++; 2257 } 2258 fContentView->TextView()->SetText(text, finish - start); 2259 free(text); 2260 2261 finish = fContentView->TextView()->CountLines(); 2262 for (int32 loop = 0; loop < finish; loop++) { 2263 fContentView->TextView()->GoToLine(loop); 2264 fContentView->TextView()->Insert((const char*)QUOTE); 2265 } 2266 2267 if (fApp->ColoredQuotes()) { 2268 const BFont* font = fContentView->TextView()->Font(); 2269 int32 length = fContentView->TextView()->TextLength(); 2270 2271 TextRunArray style(length / 8 + 8); 2272 2273 FillInQuoteTextRuns(fContentView->TextView(), NULL, 2274 fContentView->TextView()->Text(), length, font, &style.Array(), 2275 style.MaxEntries()); 2276 2277 fContentView->TextView()->SetRunArray(0, length, &style.Array()); 2278 } 2279 2280 fContentView->TextView()->GoToLine(0); 2281 if (preamble.Length() > 0) 2282 fContentView->TextView()->Insert(preamble); 2283 } else { 2284 fContentView->TextView()->LoadMessage(mail, true, preamble); 2285 } 2286 2287 fReplying = true; 2288 } 2289 2290 2291 status_t 2292 TMailWindow::Send(bool now) 2293 { 2294 if (!now) { 2295 status_t status = SaveAsDraft(); 2296 if (status != B_OK) { 2297 beep(); 2298 BAlert* alert = new BAlert("", B_TRANSLATE("E-mail draft could " 2299 "not be saved!"), B_TRANSLATE("OK")); 2300 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2301 alert->Go(); 2302 } 2303 return status; 2304 } 2305 2306 uint32 characterSetToUse = _CurrentCharacterSet(); 2307 mail_encoding encodingForBody = quoted_printable; 2308 mail_encoding encodingForHeaders = quoted_printable; 2309 2310 // Set up the encoding to use for converting binary to printable ASCII. 2311 // Normally this will be quoted printable, but for some old software, 2312 // particularly Japanese stuff, they only understand base64. They also 2313 // prefer it for the smaller size. Later on this will be reduced to 7bit 2314 // if the encoded text is just 7bit characters. 2315 if (characterSetToUse == B_SJIS_CONVERSION 2316 || characterSetToUse == B_EUC_CONVERSION) 2317 encodingForBody = base64; 2318 else if (characterSetToUse == B_JIS_CONVERSION 2319 || characterSetToUse == B_MAIL_US_ASCII_CONVERSION 2320 || characterSetToUse == B_ISO1_CONVERSION 2321 || characterSetToUse == B_EUC_KR_CONVERSION) 2322 encodingForBody = eight_bit; 2323 2324 // Using quoted printable headers on almost completely non-ASCII Japanese 2325 // is a waste of time. Besides, some stupid cell phone services need 2326 // base64 in the headers. 2327 if (characterSetToUse == B_SJIS_CONVERSION 2328 || characterSetToUse == B_EUC_CONVERSION 2329 || characterSetToUse == B_JIS_CONVERSION 2330 || characterSetToUse == B_EUC_KR_CONVERSION) 2331 encodingForHeaders = base64; 2332 2333 // Count the number of characters in the message body which aren't in the 2334 // currently selected character set. Also see if the resulting encoded 2335 // text can safely use 7 bit characters. 2336 if (fContentView->TextView()->TextLength() > 0) { 2337 // First do a trial encoding with the user's character set. 2338 int32 converterState = 0; 2339 int32 originalLength; 2340 BString tempString; 2341 int32 tempStringLength; 2342 char* tempStringPntr; 2343 originalLength = fContentView->TextView()->TextLength(); 2344 tempStringLength = originalLength * 6; 2345 // Some character sets bloat up on escape codes 2346 tempStringPntr = tempString.LockBuffer (tempStringLength); 2347 if (tempStringPntr != NULL && mail_convert_from_utf8(characterSetToUse, 2348 fContentView->TextView()->Text(), &originalLength, 2349 tempStringPntr, &tempStringLength, &converterState, 2350 0x1A /* used for unknown characters */) == B_OK) { 2351 // Check for any characters which don't fit in a 7 bit encoding. 2352 int i; 2353 bool has8Bit = false; 2354 for (i = 0; i < tempStringLength; i++) { 2355 if (tempString[i] == 0 || (tempString[i] & 0x80)) { 2356 has8Bit = true; 2357 break; 2358 } 2359 } 2360 if (!has8Bit) 2361 encodingForBody = seven_bit; 2362 tempString.UnlockBuffer (tempStringLength); 2363 2364 // Count up the number of unencoded characters and warn the user 2365 if (fApp->WarnAboutUnencodableCharacters()) { 2366 // TODO: ideally, the encoding should be silently changed to 2367 // one that can express this character 2368 int32 offset = 0; 2369 int count = 0; 2370 while (offset >= 0) { 2371 offset = tempString.FindFirst (0x1A, offset); 2372 if (offset >= 0) { 2373 count++; 2374 offset++; 2375 // Don't get stuck finding the same character again. 2376 } 2377 } 2378 if (count > 0) { 2379 int32 userAnswer; 2380 BString messageString; 2381 BString countString; 2382 countString << count; 2383 messageString << B_TRANSLATE("Your main text contains %ld" 2384 " unencodable characters. Perhaps a different " 2385 "character set would work better? Hit Send to send it " 2386 "anyway " 2387 "(a substitute character will be used in place of " 2388 "the unencodable ones), or choose Cancel to go back " 2389 "and try fixing it up."); 2390 messageString.ReplaceFirst("%ld", countString); 2391 BAlert* alert = new BAlert("Question", messageString.String(), 2392 B_TRANSLATE("Send"), 2393 B_TRANSLATE("Cancel"), 2394 NULL, B_WIDTH_AS_USUAL, B_OFFSET_SPACING, 2395 B_WARNING_ALERT); 2396 alert->SetShortcut(1, B_ESCAPE); 2397 userAnswer = alert->Go(); 2398 2399 if (userAnswer == 1) { 2400 // Cancel was picked. 2401 return -1; 2402 } 2403 } 2404 } 2405 } 2406 } 2407 2408 Hide(); 2409 // depending on the system (and I/O) load, this could take a while 2410 // but the user shouldn't be left waiting 2411 2412 status_t result; 2413 2414 if (fResending) { 2415 BFile file(fRef, O_RDONLY); 2416 result = file.InitCheck(); 2417 if (result == B_OK) { 2418 BEmailMessage mail(&file); 2419 mail.SetTo(fHeaderView->To(), characterSetToUse, 2420 encodingForHeaders); 2421 2422 if (fHeaderView->AccountID() != ~0L) 2423 mail.SendViaAccount(fHeaderView->AccountID()); 2424 2425 result = mail.Send(now); 2426 } 2427 } else { 2428 if (fMail == NULL) 2429 // the mail will be deleted when the window is closed 2430 fMail = new BEmailMessage; 2431 2432 // Had an embarrassing bug where replying to a message and clearing the 2433 // CC field meant that it got sent out anyway, so pass in empty strings 2434 // when changing the header to force it to remove the header. 2435 2436 fMail->SetTo(fHeaderView->To(), characterSetToUse, encodingForHeaders); 2437 fMail->SetSubject(fHeaderView->Subject(), characterSetToUse, 2438 encodingForHeaders); 2439 fMail->SetCC(fHeaderView->Cc(), characterSetToUse, encodingForHeaders); 2440 fMail->SetBCC(fHeaderView->Bcc()); 2441 2442 //--- Add X-Mailer field 2443 { 2444 // get app version 2445 version_info info; 2446 memset(&info, 0, sizeof(version_info)); 2447 2448 app_info appInfo; 2449 if (be_app->GetAppInfo(&appInfo) == B_OK) { 2450 BFile file(&appInfo.ref, B_READ_ONLY); 2451 if (file.InitCheck() == B_OK) { 2452 BAppFileInfo appFileInfo(&file); 2453 if (appFileInfo.InitCheck() == B_OK) 2454 appFileInfo.GetVersionInfo(&info, B_APP_VERSION_KIND); 2455 } 2456 } 2457 2458 char versionString[255]; 2459 sprintf(versionString, 2460 "Mail/Haiku %" B_PRIu32 ".%" B_PRIu32 ".%" B_PRIu32, 2461 info.major, info.middle, info.minor); 2462 fMail->SetHeaderField("X-Mailer", versionString); 2463 } 2464 2465 /****/ 2466 2467 // the content text is always added to make sure there is a mail body 2468 fMail->SetBodyTextTo(""); 2469 fContentView->TextView()->AddAsContent(fMail, fApp->WrapMode(), 2470 characterSetToUse, encodingForBody); 2471 2472 if (fEnclosuresView != NULL) { 2473 TListItem* item; 2474 int32 index = 0; 2475 while ((item = (TListItem*)fEnclosuresView->fList->ItemAt(index++)) 2476 != NULL) { 2477 if (item->Component()) 2478 continue; 2479 2480 // leave out missing enclosures 2481 BEntry entry(item->Ref()); 2482 if (!entry.Exists()) 2483 continue; 2484 2485 fMail->Attach(item->Ref(), fApp->AttachAttributes()); 2486 } 2487 } 2488 if (fHeaderView->AccountID() != ~0L) 2489 fMail->SendViaAccount(fHeaderView->AccountID()); 2490 2491 result = fMail->Send(now); 2492 2493 if (fReplying) { 2494 // Set status of the replied mail 2495 2496 BNode node(&fRepliedMail); 2497 if (node.InitCheck() >= B_OK) { 2498 if (fOriginatingWindow) { 2499 BMessage msg(M_SAVE_POSITION), reply; 2500 fOriginatingWindow->SendMessage(&msg, &reply); 2501 } 2502 WriteAttrString(&node, B_MAIL_ATTR_STATUS, "Replied"); 2503 } 2504 } 2505 } 2506 2507 bool close = false; 2508 BString errorMessage; 2509 2510 switch (result) { 2511 case B_OK: 2512 close = true; 2513 fSent = true; 2514 2515 // If it's a draft, remove the draft file 2516 if (fDraft) { 2517 BEntry entry(fRef); 2518 entry.Remove(); 2519 } 2520 break; 2521 2522 case B_MAIL_NO_DAEMON: 2523 { 2524 close = true; 2525 fSent = true; 2526 2527 BAlert* alert = new BAlert("no daemon", 2528 B_TRANSLATE("The mail_daemon is not running. The message is " 2529 "queued and will be sent when the mail_daemon is started."), 2530 B_TRANSLATE("Start now"), B_TRANSLATE("OK")); 2531 alert->SetShortcut(1, B_ESCAPE); 2532 int32 start = alert->Go(); 2533 2534 if (start == 0) { 2535 BMailDaemon daemon; 2536 result = daemon.Launch(); 2537 if (result == B_OK) { 2538 daemon.SendQueuedMail(); 2539 } else { 2540 errorMessage 2541 << B_TRANSLATE("The mail_daemon could not be " 2542 "started:\n\t") 2543 << strerror(result); 2544 } 2545 } 2546 break; 2547 } 2548 2549 // case B_MAIL_UNKNOWN_HOST: 2550 // case B_MAIL_ACCESS_ERROR: 2551 // sprintf(errorMessage, 2552 // "An error occurred trying to connect with the SMTP " 2553 // "host. Check your SMTP host name."); 2554 // break; 2555 // 2556 // case B_MAIL_NO_RECIPIENT: 2557 // sprintf(errorMessage, 2558 // "You must have either a \"To\" or \"Bcc\" recipient."); 2559 // break; 2560 2561 default: 2562 errorMessage << "An error occurred trying to send mail:\n\t" 2563 << strerror(result); 2564 break; 2565 } 2566 2567 if (result != B_NO_ERROR && result != B_MAIL_NO_DAEMON) { 2568 beep(); 2569 BAlert* alert = new BAlert("", errorMessage.String(), 2570 B_TRANSLATE("OK")); 2571 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2572 alert->Go(); 2573 } 2574 if (close) { 2575 PostMessage(B_QUIT_REQUESTED); 2576 } else { 2577 // The window was hidden earlier 2578 Show(); 2579 } 2580 2581 return result; 2582 } 2583 2584 2585 status_t 2586 TMailWindow::SaveAsDraft() 2587 { 2588 BPath draftPath; 2589 BDirectory dir; 2590 BFile draft; 2591 uint32 flags = 0; 2592 2593 if (fDraft) { 2594 status_t status = draft.SetTo(fRef, 2595 B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 2596 if (status != B_OK) 2597 return status; 2598 } else { 2599 // Get the user home directory 2600 status_t status = find_directory(B_USER_DIRECTORY, &draftPath); 2601 if (status != B_OK) 2602 return status; 2603 2604 // Append the relative path of the draft directory 2605 draftPath.Append(kDraftPath); 2606 2607 // Create the file 2608 status = dir.SetTo(draftPath.Path()); 2609 switch (status) { 2610 // Create the directory if it does not exist 2611 case B_ENTRY_NOT_FOUND: 2612 if ((status = dir.CreateDirectory(draftPath.Path(), &dir)) 2613 != B_OK) 2614 return status; 2615 case B_OK: 2616 { 2617 char fileName[B_FILE_NAME_LENGTH]; 2618 // save as some version of the message's subject 2619 if (fHeaderView->IsSubjectEmpty()) { 2620 strlcpy(fileName, B_TRANSLATE("Untitled"), 2621 sizeof(fileName)); 2622 } else { 2623 strlcpy(fileName, fHeaderView->Subject(), sizeof(fileName)); 2624 } 2625 2626 uint32 originalLength = strlen(fileName); 2627 2628 // convert /, \ and : to - 2629 for (char* bad = fileName; (bad = strchr(bad, '/')) != NULL; 2630 ++bad) { 2631 *bad = '-'; 2632 } 2633 for (char* bad = fileName; (bad = strchr(bad, '\\')) != NULL; 2634 ++bad) { 2635 *bad = '-'; 2636 } 2637 for (char* bad = fileName; (bad = strchr(bad, ':')) != NULL; 2638 ++bad) { 2639 *bad = '-'; 2640 } 2641 2642 // Create the file; if the name exists, find a unique name 2643 flags = B_WRITE_ONLY | B_CREATE_FILE | B_FAIL_IF_EXISTS; 2644 int32 i = 1; 2645 do { 2646 status = draft.SetTo(&dir, fileName, flags); 2647 if (status == B_OK) 2648 break; 2649 char appendix[B_FILE_NAME_LENGTH]; 2650 sprintf(appendix, " %" B_PRId32, i++); 2651 int32 pos = min_c(sizeof(fileName) - strlen(appendix), 2652 originalLength); 2653 sprintf(fileName + pos, "%s", appendix); 2654 } while (status == B_FILE_EXISTS); 2655 if (status != B_OK) 2656 return status; 2657 2658 // Cache the ref 2659 if (fRef == NULL) 2660 fRef = new entry_ref; 2661 BEntry entry(&dir, fileName); 2662 entry.GetRef(fRef); 2663 break; 2664 } 2665 default: 2666 return status; 2667 } 2668 } 2669 2670 // Write the content of the message 2671 draft.Write(fContentView->TextView()->Text(), 2672 fContentView->TextView()->TextLength()); 2673 2674 // Add the header stuff as attributes 2675 WriteAttrString(&draft, B_MAIL_ATTR_NAME, fHeaderView->To()); 2676 WriteAttrString(&draft, B_MAIL_ATTR_TO, fHeaderView->To()); 2677 WriteAttrString(&draft, B_MAIL_ATTR_SUBJECT, fHeaderView->Subject()); 2678 if (!fHeaderView->IsCcEmpty()) 2679 WriteAttrString(&draft, B_MAIL_ATTR_CC, fHeaderView->Cc()); 2680 if (!fHeaderView->IsBccEmpty()) 2681 WriteAttrString(&draft, B_MAIL_ATTR_BCC, fHeaderView->Bcc()); 2682 2683 // Add account 2684 if (fHeaderView->AccountName() != NULL) { 2685 WriteAttrString(&draft, B_MAIL_ATTR_ACCOUNT, 2686 fHeaderView->AccountName()); 2687 } 2688 2689 // Add encoding 2690 BMenuItem* menuItem = fEncodingMenu->FindMarked(); 2691 if (menuItem != NULL) 2692 WriteAttrString(&draft, "MAIL:encoding", menuItem->Label()); 2693 2694 // Add the draft attribute for indexing 2695 uint32 draftAttr = true; 2696 draft.WriteAttr("MAIL:draft", B_INT32_TYPE, 0, &draftAttr, sizeof(uint32)); 2697 2698 // Add Attachment paths in attribute 2699 if (fEnclosuresView != NULL) { 2700 TListItem* item; 2701 BString pathStr; 2702 2703 for (int32 i = 0; (item = (TListItem*)fEnclosuresView->fList->ItemAt(i)) 2704 != NULL; i++) { 2705 if (i > 0) 2706 pathStr.Append(":"); 2707 2708 BEntry entry(item->Ref(), true); 2709 if (!entry.Exists()) 2710 continue; 2711 2712 BPath path; 2713 entry.GetPath(&path); 2714 pathStr.Append(path.Path()); 2715 } 2716 if (pathStr.Length()) 2717 draft.WriteAttrString("MAIL:attachments", &pathStr); 2718 } 2719 2720 // Set the MIME Type of the file 2721 BNodeInfo info(&draft); 2722 info.SetType(kDraftType); 2723 2724 fDraft = true; 2725 fChanged = false; 2726 2727 fToolBar->SetActionEnabled(M_SAVE_AS_DRAFT, false); 2728 2729 return B_OK; 2730 } 2731 2732 2733 status_t 2734 TMailWindow::TrainMessageAs(const char* commandWord) 2735 { 2736 status_t errorCode = -1; 2737 BEntry fileEntry; 2738 BPath filePath; 2739 BMessage replyMessage; 2740 BMessage scriptingMessage; 2741 team_id serverTeam; 2742 2743 if (fRef == NULL) 2744 goto ErrorExit; // Need to have a real file and name. 2745 errorCode = fileEntry.SetTo(fRef, true); 2746 if (errorCode != B_OK) 2747 goto ErrorExit; 2748 errorCode = fileEntry.GetPath(&filePath); 2749 if (errorCode != B_OK) 2750 goto ErrorExit; 2751 fileEntry.Unset(); 2752 2753 // Get a connection to the spam database server. Launch if needed. 2754 2755 if (!fMessengerToSpamServer.IsValid()) { 2756 // Make sure the server is running. 2757 if (!be_roster->IsRunning (kSpamServerSignature)) { 2758 errorCode = be_roster->Launch (kSpamServerSignature); 2759 if (errorCode != B_OK) { 2760 BPath path; 2761 entry_ref ref; 2762 directory_which places[] = {B_SYSTEM_NONPACKAGED_BIN_DIRECTORY, 2763 B_SYSTEM_BIN_DIRECTORY}; 2764 for (int32 i = 0; i < 2; i++) { 2765 find_directory(places[i],&path); 2766 path.Append("spamdbm"); 2767 if (!BEntry(path.Path()).Exists()) 2768 continue; 2769 get_ref_for_path(path.Path(),&ref); 2770 2771 errorCode = be_roster->Launch(&ref); 2772 if (errorCode == B_OK) 2773 break; 2774 } 2775 if (errorCode != B_OK) 2776 goto ErrorExit; 2777 } 2778 } 2779 2780 // Set up the messenger to the database server. 2781 errorCode = B_SERVER_NOT_FOUND; 2782 serverTeam = be_roster->TeamFor(kSpamServerSignature); 2783 if (serverTeam < 0) 2784 goto ErrorExit; 2785 2786 fMessengerToSpamServer = BMessenger (kSpamServerSignature, serverTeam, 2787 &errorCode); 2788 2789 if (!fMessengerToSpamServer.IsValid()) 2790 goto ErrorExit; 2791 } 2792 2793 // Ask the server to train on the message. Give it the command word and 2794 // the absolute path name to use. 2795 2796 scriptingMessage.MakeEmpty(); 2797 scriptingMessage.what = B_SET_PROPERTY; 2798 scriptingMessage.AddSpecifier(commandWord); 2799 errorCode = scriptingMessage.AddData("data", B_STRING_TYPE, 2800 filePath.Path(), strlen(filePath.Path()) + 1, false); 2801 if (errorCode != B_OK) 2802 goto ErrorExit; 2803 replyMessage.MakeEmpty(); 2804 errorCode = fMessengerToSpamServer.SendMessage(&scriptingMessage, 2805 &replyMessage); 2806 if (errorCode != B_OK 2807 || replyMessage.FindInt32("error", &errorCode) != B_OK 2808 || errorCode != B_OK) 2809 goto ErrorExit; // Classification failed in one of many ways. 2810 2811 SetTitleForMessage(); 2812 // Update window title to show new spam classification. 2813 return B_OK; 2814 2815 ErrorExit: 2816 beep(); 2817 char errorString[1500]; 2818 snprintf(errorString, sizeof(errorString), "Unable to train the message " 2819 "file \"%s\" as %s. Possibly useful error code: %s (%" B_PRId32 ").", 2820 filePath.Path(), commandWord, strerror(errorCode), errorCode); 2821 BAlert* alert = new BAlert("", errorString, B_TRANSLATE("OK")); 2822 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2823 alert->Go(); 2824 2825 return errorCode; 2826 } 2827 2828 2829 void 2830 TMailWindow::SetTitleForMessage() 2831 { 2832 // Figure out the title of this message and set the title bar 2833 BString title = B_TRANSLATE_SYSTEM_NAME("Mail"); 2834 2835 if (fIncoming) { 2836 if (fMail->GetName(&title) == B_OK) 2837 title << ": \"" << fMail->Subject() << "\""; 2838 else 2839 title = fMail->Subject(); 2840 2841 if (fDownloading) 2842 title.Prepend("Downloading: "); 2843 2844 if (fApp->ShowSpamGUI() && fRef != NULL) { 2845 BString classification; 2846 BNode node(fRef); 2847 char numberString[30]; 2848 BString oldTitle(title); 2849 float spamRatio; 2850 if (node.InitCheck() != B_OK || node.ReadAttrString( 2851 "MAIL:classification", &classification) != B_OK) 2852 classification = "Unrated"; 2853 if (classification != "Spam" && classification != "Genuine") { 2854 // Uncertain, Unrated and other unknown classes, show the ratio. 2855 if (node.InitCheck() == B_OK && node.ReadAttr("MAIL:ratio_spam", 2856 B_FLOAT_TYPE, 0, &spamRatio, sizeof(spamRatio)) 2857 == sizeof(spamRatio)) { 2858 sprintf(numberString, "%.4f", spamRatio); 2859 classification << " " << numberString; 2860 } 2861 } 2862 title = ""; 2863 title << "[" << classification << "] " << oldTitle; 2864 } 2865 } 2866 SetTitle(title); 2867 } 2868 2869 2870 /*! Open *another* message in the existing mail window. Some code here is 2871 duplicated from various constructors. 2872 TODO: The duplicated code should be moved to a private initializer method 2873 */ 2874 status_t 2875 TMailWindow::OpenMessage(const entry_ref* ref, uint32 characterSetForDecoding) 2876 { 2877 if (ref == NULL) 2878 return B_ERROR; 2879 2880 // Set some references to the email file 2881 delete fRef; 2882 fRef = new entry_ref(*ref); 2883 2884 fPrevTrackerPositionSaved = false; 2885 fNextTrackerPositionSaved = false; 2886 2887 fContentView->TextView()->StopLoad(); 2888 delete fMail; 2889 fMail = NULL; 2890 2891 BFile file(fRef, B_READ_ONLY); 2892 status_t err = file.InitCheck(); 2893 if (err != B_OK) 2894 return err; 2895 2896 char mimeType[256]; 2897 BNodeInfo fileInfo(&file); 2898 fileInfo.GetType(mimeType); 2899 2900 if (strcmp(mimeType, B_PARTIAL_MAIL_TYPE) == 0) { 2901 BMessenger listener(this); 2902 status_t status = BMailDaemon().FetchBody(*ref, &listener); 2903 if (status != B_OK) 2904 fprintf(stderr, "Could not fetch body: %s\n", strerror(status)); 2905 fileInfo.GetType(mimeType); 2906 _SetDownloading(true); 2907 } else 2908 _SetDownloading(false); 2909 2910 // Check if it's a draft file, which contains only the text, and has the 2911 // from, to, bcc, attachments listed as attributes. 2912 if (strcmp(kDraftType, mimeType) == 0) { 2913 BNode node(fRef); 2914 off_t size; 2915 BString string; 2916 2917 fMail = new BEmailMessage; // Not really used much, but still needed. 2918 2919 // Load the raw UTF-8 text from the file. 2920 file.GetSize(&size); 2921 fContentView->TextView()->SetText(&file, 0, size); 2922 2923 // Restore Fields from attributes 2924 if (node.ReadAttrString(B_MAIL_ATTR_TO, &string) == B_OK) 2925 fHeaderView->SetTo(string); 2926 if (node.ReadAttrString(B_MAIL_ATTR_SUBJECT, &string) == B_OK) 2927 fHeaderView->SetSubject(string); 2928 if (node.ReadAttrString(B_MAIL_ATTR_CC, &string) == B_OK) 2929 fHeaderView->SetCc(string); 2930 if (node.ReadAttrString(B_MAIL_ATTR_BCC, &string) == B_OK) 2931 fHeaderView->SetBcc(string); 2932 2933 // Restore account 2934 if (node.ReadAttrString(B_MAIL_ATTR_ACCOUNT, &string) == B_OK) 2935 fHeaderView->SetAccount(string); 2936 2937 // Restore encoding 2938 if (node.ReadAttrString("MAIL:encoding", &string) == B_OK) { 2939 BMenuItem* encodingItem = fEncodingMenu->FindItem(string.String()); 2940 if (encodingItem != NULL) 2941 encodingItem->SetMarked(true); 2942 } 2943 2944 // Restore attachments 2945 if (node.ReadAttrString("MAIL:attachments", &string) == B_OK) { 2946 BMessage msg(REFS_RECEIVED); 2947 entry_ref enc_ref; 2948 2949 BStringList list; 2950 string.Split(":", false, list); 2951 for (int32 i = 0; i < list.CountStrings(); i++) { 2952 BEntry entry(list.StringAt(i), true); 2953 if (entry.Exists()) { 2954 entry.GetRef(&enc_ref); 2955 msg.AddRef("refs", &enc_ref); 2956 } 2957 } 2958 AddEnclosure(&msg); 2959 } 2960 2961 // restore the reading position if available 2962 PostMessage(M_READ_POS); 2963 2964 PostMessage(RESET_BUTTONS); 2965 fIncoming = false; 2966 fDraft = true; 2967 } else { 2968 // A real mail message, parse its headers to get from, to, etc. 2969 fMail = new BEmailMessage(fRef, characterSetForDecoding); 2970 fIncoming = true; 2971 fHeaderView->SetFromMessage(fMail); 2972 } 2973 2974 err = fMail->InitCheck(); 2975 if (err < B_OK) { 2976 delete fMail; 2977 fMail = NULL; 2978 return err; 2979 } 2980 2981 SetTitleForMessage(); 2982 2983 if (fIncoming) { 2984 // Put the addresses in the 'Save Address' Menu 2985 BMenuItem* item; 2986 while ((item = fSaveAddrMenu->RemoveItem((int32)0)) != NULL) 2987 delete item; 2988 2989 // create the list of addresses 2990 2991 BList addressList; 2992 get_address_list(addressList, fMail->To(), extract_address); 2993 get_address_list(addressList, fMail->CC(), extract_address); 2994 get_address_list(addressList, fMail->From(), extract_address); 2995 get_address_list(addressList, fMail->ReplyTo(), extract_address); 2996 2997 BMessage* msg; 2998 2999 for (int32 i = addressList.CountItems(); i-- > 0;) { 3000 char* address = (char*)addressList.RemoveItem((int32)0); 3001 3002 // insert the new address in alphabetical order 3003 int32 index = 0; 3004 while ((item = fSaveAddrMenu->ItemAt(index)) != NULL) { 3005 if (!strcmp(address, item->Label())) { 3006 // item already in list 3007 goto skip; 3008 } 3009 3010 if (strcmp(address, item->Label()) < 0) 3011 break; 3012 3013 index++; 3014 } 3015 3016 msg = new BMessage(M_SAVE); 3017 msg->AddString("address", address); 3018 fSaveAddrMenu->AddItem(new BMenuItem(address, msg), index); 3019 3020 skip: 3021 free(address); 3022 } 3023 3024 // Clear out existing contents of text view. 3025 fContentView->TextView()->SetText("", (int32)0); 3026 3027 fContentView->TextView()->LoadMessage(fMail, false, NULL); 3028 3029 if (fApp->ShowToolBar()) 3030 _UpdateReadButton(); 3031 } 3032 3033 return B_OK; 3034 } 3035 3036 3037 TMailWindow* 3038 TMailWindow::FrontmostWindow() 3039 { 3040 BAutolock locker(sWindowListLock); 3041 if (sWindowList.CountItems() > 0) 3042 return (TMailWindow*)sWindowList.ItemAt(0); 3043 3044 return NULL; 3045 } 3046 3047 3048 // #pragma mark - 3049 3050 3051 status_t 3052 TMailWindow::_GetQueryPath(BPath* queryPath) const 3053 { 3054 // get the user home directory and from there the query folder 3055 status_t ret = find_directory(B_USER_DIRECTORY, queryPath); 3056 if (ret == B_OK) 3057 ret = queryPath->Append(kQueriesDirectory); 3058 3059 return ret; 3060 } 3061 3062 3063 void 3064 TMailWindow::_RebuildQueryMenu(bool firstTime) 3065 { 3066 while (fQueryMenu->ItemAt(0)) { 3067 BMenuItem* item = fQueryMenu->RemoveItem((int32)0); 3068 delete item; 3069 } 3070 3071 fQueryMenu->AddItem(new BMenuItem(kSameRecipientItem, 3072 new BMessage(M_QUERY_RECIPIENT))); 3073 fQueryMenu->AddItem(new BMenuItem(kSameSenderItem, 3074 new BMessage(M_QUERY_SENDER))); 3075 fQueryMenu->AddItem(new BMenuItem(kSameSubjectItem, 3076 new BMessage(M_QUERY_SUBJECT))); 3077 3078 BPath queryPath; 3079 if (_GetQueryPath(&queryPath) < B_OK) 3080 return; 3081 3082 BDirectory queryDir(queryPath.Path()); 3083 3084 if (firstTime) { 3085 BPrivate::BPathMonitor::StartWatching(queryPath.Path(), 3086 B_WATCH_RECURSIVELY, BMessenger(this, this)); 3087 } 3088 3089 // If we find the named query, add it to the menu. 3090 BEntry entry; 3091 while (queryDir.GetNextEntry(&entry) == B_OK) { 3092 char name[B_FILE_NAME_LENGTH + 1]; 3093 entry.GetName(name); 3094 3095 char* queryString = _BuildQueryString(&entry); 3096 if (queryString == NULL) 3097 continue; 3098 3099 QueryMenu* queryMenu = new QueryMenu(name, false); 3100 queryMenu->SetTargetForItems(be_app); 3101 queryMenu->SetPredicate(queryString); 3102 fQueryMenu->AddItem(queryMenu); 3103 3104 free(queryString); 3105 } 3106 3107 fQueryMenu->AddItem(new BSeparatorItem()); 3108 3109 fQueryMenu->AddItem(new BMenuItem(B_TRANSLATE("Edit queries" 3110 B_UTF8_ELLIPSIS), 3111 new BMessage(M_EDIT_QUERIES), 'E', B_SHIFT_KEY)); 3112 } 3113 3114 3115 char* 3116 TMailWindow::_BuildQueryString(BEntry* entry) const 3117 { 3118 BNode node(entry); 3119 if (node.InitCheck() != B_OK) 3120 return NULL; 3121 3122 uint32 mode; 3123 if (node.ReadAttr(kAttrQueryInitialMode, B_INT32_TYPE, 0, (int32*)&mode, 3124 sizeof(int32)) <= 0) { 3125 mode = kByNameItem; 3126 } 3127 3128 BString queryString; 3129 switch (mode) { 3130 case kByForumlaItem: 3131 { 3132 BString buffer; 3133 if (node.ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK) 3134 queryString << buffer; 3135 break; 3136 } 3137 3138 case kByNameItem: 3139 { 3140 BString buffer; 3141 if (node.ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK) 3142 queryString << "(name==*" << buffer << "*)"; 3143 break; 3144 } 3145 3146 case kByAttributeItem: 3147 { 3148 int32 count = 1; 3149 if (node.ReadAttr(kAttrQueryInitialNumAttrs, B_INT32_TYPE, 0, 3150 (int32*)&count, sizeof(int32)) <= 0) { 3151 count = 1; 3152 } 3153 3154 attr_info info; 3155 if (node.GetAttrInfo(kAttrQueryInitialAttrs, &info) != B_OK) 3156 break; 3157 3158 if (count > 1) 3159 queryString << "("; 3160 3161 char* buffer = new char[info.size]; 3162 if (node.ReadAttr(kAttrQueryInitialAttrs, B_MESSAGE_TYPE, 0, 3163 buffer, (size_t)info.size) == info.size) { 3164 BMessage message; 3165 if (message.Unflatten(buffer) == B_OK) { 3166 for (int32 index = 0; /*index < count*/; index++) { 3167 const char* field; 3168 const char* value; 3169 if (message.FindString("menuSelection", index, &field) 3170 != B_OK 3171 || message.FindString("attrViewText", index, &value) 3172 != B_OK) { 3173 break; 3174 } 3175 3176 // ignore the mime type, we'll force it to be email 3177 // later 3178 if (strcmp(field, "BEOS:TYPE") != 0) { 3179 // TODO: check if subMenu contains the type of 3180 // comparison we are suppose to make here 3181 queryString << "(" << field << "==\"" 3182 << value << "\")"; 3183 3184 int32 logicMenuSelectedIndex; 3185 if (message.FindInt32("logicalRelation", index, 3186 &logicMenuSelectedIndex) == B_OK) { 3187 if (logicMenuSelectedIndex == 0) 3188 queryString << "&&"; 3189 else if (logicMenuSelectedIndex == 1) 3190 queryString << "||"; 3191 } else 3192 break; 3193 } 3194 } 3195 } 3196 } 3197 3198 if (count > 1) 3199 queryString << ")"; 3200 3201 delete [] buffer; 3202 break; 3203 } 3204 3205 default: 3206 break; 3207 } 3208 3209 if (queryString.Length() == 0) 3210 return NULL; 3211 3212 // force it to check for email only 3213 if (queryString.FindFirst("text/x-email") < 0) { 3214 BString temp; 3215 temp << "(" << queryString << "&&(BEOS:TYPE==\"text/x-email\"))"; 3216 queryString = temp; 3217 } 3218 3219 return strdup(queryString.String()); 3220 } 3221 3222 3223 void 3224 TMailWindow::_LaunchQuery(const char* title, const char* attribute, 3225 BString text) 3226 { 3227 /* ToDo: 3228 If the search attribute is To or From, it'd be nice to parse the 3229 search text to separate the email address and user name. 3230 Then search for 'name || address' to get all mails of people, 3231 never mind the account or mail config they sent from. 3232 */ 3233 text.ReplaceAll(" ", "*"); // query on MAIL:track demands * for space 3234 text.ReplaceAll("\"", "\\\""); 3235 3236 BString* term = new BString("(("); 3237 term->Append(attribute); 3238 term->Append("==\"*"); 3239 term->Append(text); 3240 term->Append("*\")&&(BEOS:TYPE==\"text/x-email\"))"); 3241 3242 BPath queryPath; 3243 if (find_directory(B_USER_CACHE_DIRECTORY, &queryPath) != B_OK) 3244 return; 3245 queryPath.Append("Mail"); 3246 if ((create_directory(queryPath.Path(), 0777)) != B_OK) 3247 return; 3248 queryPath.Append(title); 3249 BFile query(queryPath.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 3250 if (query.InitCheck() != B_OK) 3251 return; 3252 3253 BNode queryNode(queryPath.Path()); 3254 if (queryNode.InitCheck() != B_OK) 3255 return; 3256 3257 // Copy layout from DefaultQueryTemplates 3258 BPath templatePath; 3259 find_directory(B_USER_SETTINGS_DIRECTORY, &templatePath); 3260 templatePath.Append("Tracker/DefaultQueryTemplates/text_x-email"); 3261 BNode templateNode(templatePath.Path()); 3262 3263 if (templateNode.InitCheck() == B_OK) { 3264 if (CopyAttributes(templateNode, queryNode) != B_OK) { 3265 syslog(LOG_INFO, "Mail: copying x-email DefaultQueryTemplate " 3266 "attributes failed"); 3267 } 3268 } 3269 3270 queryNode.WriteAttrString("_trk/qrystr", term); 3271 BNodeInfo nodeInfo(&queryNode); 3272 nodeInfo.SetType("application/x-vnd.Be-query"); 3273 3274 // Launch query 3275 BEntry entry(queryPath.Path()); 3276 entry_ref ref; 3277 if (entry.GetRef(&ref) == B_OK) 3278 be_roster->Launch(&ref); 3279 } 3280 3281 3282 void 3283 TMailWindow::_AddReadButton() 3284 { 3285 BNode node(fRef); 3286 3287 read_flags flag = B_UNREAD; 3288 read_read_attr(node, flag); 3289 3290 if (flag == B_READ) { 3291 fToolBar->SetActionVisible(M_UNREAD, true); 3292 fToolBar->SetActionVisible(M_READ, false); 3293 } else { 3294 fToolBar->SetActionVisible(M_UNREAD, false); 3295 fToolBar->SetActionVisible(M_READ, true); 3296 } 3297 } 3298 3299 3300 void 3301 TMailWindow::_UpdateReadButton() 3302 { 3303 if (fApp->ShowToolBar()) { 3304 if (!fAutoMarkRead && fIncoming) 3305 _AddReadButton(); 3306 } 3307 UpdateViews(); 3308 } 3309 3310 3311 void 3312 TMailWindow::_UpdateLabel(uint32 command, const char* label, bool show) 3313 { 3314 BButton* button = fToolBar->FindButton(command); 3315 if (button != NULL) { 3316 button->SetLabel(show ? label : NULL); 3317 button->SetToolTip(show ? NULL : label); 3318 } 3319 } 3320 3321 3322 void 3323 TMailWindow::_SetDownloading(bool downloading) 3324 { 3325 fDownloading = downloading; 3326 } 3327 3328 3329 uint32 3330 TMailWindow::_CurrentCharacterSet() const 3331 { 3332 uint32 defaultCharSet = fResending || !fIncoming 3333 ? fApp->MailCharacterSet() : B_MAIL_NULL_CONVERSION; 3334 3335 BMenuItem* marked = fEncodingMenu->FindMarked(); 3336 if (marked == NULL) 3337 return defaultCharSet; 3338 3339 return marked->Message()->GetInt32("charset", defaultCharSet); 3340 } 3341