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