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