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