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 .AddGroup(B_VERTICAL, 0) 561 .Add(fToolBar) 562 .Add(fHeaderView) 563 .SetInsets(B_USE_WINDOW_SPACING, 0, B_USE_WINDOW_SPACING, 0) 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 PostMessage(B_QUIT_REQUESTED); 1040 break; 1041 } 1042 1043 entry_ref ref; 1044 if (msg->FindRef("ref", &ref) != B_OK) 1045 break; 1046 if (ref != *fRef) 1047 break; 1048 1049 // reload the current message 1050 OpenMessage(&ref, _CurrentCharacterSet()); 1051 break; 1052 } 1053 1054 case FIELD_CHANGED: 1055 { 1056 int32 prevState = fFieldState; 1057 int32 fieldMask = msg->FindInt32("bitmask"); 1058 void* source; 1059 1060 if (msg->FindPointer("source", &source) == B_OK) { 1061 int32 length; 1062 1063 if (fieldMask == FIELD_BODY) 1064 length = ((TTextView*)source)->TextLength(); 1065 else 1066 length = ((AddressTextControl*)source)->TextLength(); 1067 1068 if (length) 1069 fFieldState |= fieldMask; 1070 else 1071 fFieldState &= ~fieldMask; 1072 } 1073 1074 // Has anything changed? 1075 if (prevState != fFieldState || !fChanged) { 1076 // Change Buttons to reflect this 1077 fToolBar->SetActionEnabled(M_SAVE_AS_DRAFT, fFieldState); 1078 fToolBar->SetActionEnabled(M_PRINT, fFieldState); 1079 fToolBar->SetActionEnabled(M_SEND_NOW, (fFieldState & FIELD_TO) 1080 || (fFieldState & FIELD_BCC)); 1081 } 1082 fChanged = true; 1083 1084 // Update title bar if "subject" has changed 1085 if (!fIncoming && (fieldMask & FIELD_SUBJECT) != 0) { 1086 // If no subject, set to "Mail" 1087 if (fHeaderView->IsSubjectEmpty()) 1088 SetTitle(B_TRANSLATE_SYSTEM_NAME("Mail")); 1089 else 1090 SetTitle(fHeaderView->Subject()); 1091 } 1092 break; 1093 } 1094 case LIST_INVOKED: 1095 PostMessage(msg, fEnclosuresView); 1096 break; 1097 1098 case CHANGE_FONT: 1099 PostMessage(msg, fContentView); 1100 break; 1101 1102 case M_NEW: 1103 { 1104 BMessage message(M_NEW); 1105 message.AddInt32("type", msg->what); 1106 be_app->PostMessage(&message); 1107 break; 1108 } 1109 1110 case M_SPAM_BUTTON: 1111 { 1112 /* 1113 A popup from a button is good only when the behavior has some 1114 consistency and there is some visual indication that a menu 1115 will be shown when clicked. A workable implementation would 1116 have an extra button attached to the main one which has a 1117 downward-pointing arrow. Mozilla Thunderbird's 'Get Mail' 1118 button is a good example of this. 1119 1120 TODO: Replace this code with a split toolbar button 1121 */ 1122 uint32 buttons; 1123 if (msg->FindInt32("buttons", (int32*)&buttons) == B_OK 1124 && buttons == B_SECONDARY_MOUSE_BUTTON) { 1125 BPopUpMenu menu("Spam Actions", false, false); 1126 for (int i = 0; i < 4; i++) 1127 menu.AddItem(new BMenuItem(kSpamMenuItemTextArray[i], 1128 new BMessage(M_TRAIN_SPAM_AND_DELETE + i))); 1129 1130 BPoint where; 1131 msg->FindPoint("where", &where); 1132 BMenuItem* item; 1133 if ((item = menu.Go(where, false, false)) != NULL) 1134 PostMessage(item->Message()); 1135 break; 1136 } else { 1137 // Default action for left clicking on the spam button. 1138 PostMessage(new BMessage(M_TRAIN_SPAM_AND_DELETE)); 1139 } 1140 break; 1141 } 1142 1143 case M_TRAIN_SPAM_AND_DELETE: 1144 PostMessage(M_DELETE_NEXT); 1145 case M_TRAIN_SPAM: 1146 TrainMessageAs("Spam"); 1147 break; 1148 1149 case M_UNTRAIN: 1150 TrainMessageAs("Uncertain"); 1151 break; 1152 1153 case M_TRAIN_GENUINE: 1154 TrainMessageAs("Genuine"); 1155 break; 1156 1157 case M_REPLY: 1158 { 1159 // TODO: This needs removed in favor of a split toolbar button. 1160 // See comments for Spam button 1161 uint32 buttons; 1162 if (msg->FindInt32("buttons", (int32*)&buttons) == B_OK 1163 && buttons == B_SECONDARY_MOUSE_BUTTON) { 1164 BPopUpMenu menu("Reply To", false, false); 1165 menu.AddItem(new BMenuItem(B_TRANSLATE("Reply"), 1166 new BMessage(M_REPLY))); 1167 menu.AddItem(new BMenuItem(B_TRANSLATE("Reply to sender"), 1168 new BMessage(M_REPLY_TO_SENDER))); 1169 menu.AddItem(new BMenuItem(B_TRANSLATE("Reply to all"), 1170 new BMessage(M_REPLY_ALL))); 1171 1172 BPoint where; 1173 msg->FindPoint("where", &where); 1174 1175 BMenuItem* item; 1176 if ((item = menu.Go(where, false, false)) != NULL) { 1177 item->SetTarget(this); 1178 PostMessage(item->Message()); 1179 } 1180 break; 1181 } 1182 // Fall through 1183 } 1184 case M_FORWARD: 1185 { 1186 // TODO: This needs removed in favor of a split toolbar button. 1187 // See comments for Spam button 1188 uint32 buttons; 1189 if (msg->FindInt32("buttons", (int32*)&buttons) == B_OK 1190 && buttons == B_SECONDARY_MOUSE_BUTTON) { 1191 BPopUpMenu menu("Forward", false, false); 1192 menu.AddItem(new BMenuItem(B_TRANSLATE("Forward"), 1193 new BMessage(M_FORWARD))); 1194 menu.AddItem(new BMenuItem( 1195 B_TRANSLATE("Forward without attachments"), 1196 new BMessage(M_FORWARD_WITHOUT_ATTACHMENTS))); 1197 1198 BPoint where; 1199 msg->FindPoint("where", &where); 1200 1201 BMenuItem* item; 1202 if ((item = menu.Go(where, false, false)) != NULL) { 1203 item->SetTarget(this); 1204 PostMessage(item->Message()); 1205 } 1206 break; 1207 } 1208 } 1209 1210 // Fall Through 1211 case M_REPLY_ALL: 1212 case M_REPLY_TO_SENDER: 1213 case M_FORWARD_WITHOUT_ATTACHMENTS: 1214 case M_RESEND: 1215 case M_COPY_TO_NEW: 1216 { 1217 BMessage message(M_NEW); 1218 message.AddRef("ref", fRef); 1219 message.AddPointer("window", this); 1220 message.AddInt32("type", msg->what); 1221 be_app->PostMessage(&message); 1222 break; 1223 } 1224 case M_DELETE: 1225 case M_DELETE_PREV: 1226 case M_DELETE_NEXT: 1227 { 1228 if (msg->what == M_DELETE_NEXT && (modifiers() & B_SHIFT_KEY) != 0) 1229 msg->what = M_DELETE_PREV; 1230 1231 bool foundRef = false; 1232 entry_ref nextRef; 1233 if ((msg->what == M_DELETE_PREV || msg->what == M_DELETE_NEXT) 1234 && fRef != NULL) { 1235 // Find the next message that should be displayed 1236 nextRef = *fRef; 1237 foundRef = GetTrackerWindowFile(&nextRef, 1238 msg->what == M_DELETE_NEXT); 1239 } 1240 if (fIncoming) { 1241 read_flags flag = (fAutoMarkRead == true) ? B_READ : B_SEEN; 1242 MarkMessageRead(fRef, flag); 1243 } 1244 1245 if (!fTrackerMessenger.IsValid() || !fIncoming) { 1246 // Not associated with a tracker window. Create a new 1247 // messenger and ask the tracker to delete this entry 1248 if (fDraft || fIncoming) { 1249 BMessenger tracker("application/x-vnd.Be-TRAK"); 1250 if (tracker.IsValid()) { 1251 BMessage msg('Ttrs'); 1252 msg.AddRef("refs", fRef); 1253 tracker.SendMessage(&msg); 1254 } else { 1255 BAlert* alert = new BAlert("", 1256 B_TRANSLATE("Need Tracker to move items to trash"), 1257 B_TRANSLATE("Sorry")); 1258 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1259 alert->Go(); 1260 } 1261 } 1262 } else { 1263 // This is associated with a tracker window. Ask the 1264 // window to delete this entry. Do it this way if we 1265 // can instead of the above way because it doesn't reset 1266 // the selection (even though we set selection below, this 1267 // still causes problems). 1268 BMessage delmsg(B_DELETE_PROPERTY); 1269 BMessage entryspec('sref'); 1270 entryspec.AddRef("refs", fRef); 1271 entryspec.AddString("property", "Entry"); 1272 delmsg.AddSpecifier(&entryspec); 1273 fTrackerMessenger.SendMessage(&delmsg); 1274 } 1275 1276 // If the next file was found, open it. If it was not, 1277 // we have no choice but to close this window. 1278 if (foundRef) { 1279 TMailWindow* window 1280 = static_cast<TMailApp*>(be_app)->FindWindow(nextRef); 1281 if (window == NULL) 1282 OpenMessage(&nextRef, _CurrentCharacterSet()); 1283 else 1284 window->Activate(); 1285 1286 SetTrackerSelectionToCurrent(); 1287 1288 if (window == NULL) 1289 break; 1290 } 1291 1292 fSent = true; 1293 BMessage msg(B_CLOSE_REQUESTED); 1294 PostMessage(&msg); 1295 break; 1296 } 1297 1298 case M_CLOSE_READ: 1299 { 1300 BMessage message(B_CLOSE_REQUESTED); 1301 message.AddString("status", "Read"); 1302 PostMessage(&message); 1303 break; 1304 } 1305 case M_CLOSE_SAVED: 1306 { 1307 BMessage message(B_QUIT_REQUESTED); 1308 message.AddString("status", "Saved"); 1309 PostMessage(&message); 1310 break; 1311 } 1312 case kMsgQuitAndKeepAllStatus: 1313 fKeepStatusOnQuit = true; 1314 be_app->PostMessage(B_QUIT_REQUESTED); 1315 break; 1316 case M_CLOSE_CUSTOM: 1317 if (msg->HasString("status")) { 1318 BMessage message(B_CLOSE_REQUESTED); 1319 message.AddString("status", msg->GetString("status")); 1320 PostMessage(&message); 1321 } else { 1322 BRect r = Frame(); 1323 r.left += ((r.Width() - STATUS_WIDTH) / 2); 1324 r.right = r.left + STATUS_WIDTH; 1325 r.top += 40; 1326 r.bottom = r.top + STATUS_HEIGHT; 1327 1328 BString string = "could not read"; 1329 BNode node(fRef); 1330 if (node.InitCheck() == B_OK) 1331 node.ReadAttrString(B_MAIL_ATTR_STATUS, &string); 1332 1333 new TStatusWindow(r, this, string.String()); 1334 } 1335 break; 1336 1337 case M_STATUS: 1338 { 1339 const char* attribute; 1340 if (msg->FindString("attribute", &attribute) != B_OK) 1341 break; 1342 1343 BMessage message(B_CLOSE_REQUESTED); 1344 message.AddString("status", attribute); 1345 PostMessage(&message); 1346 break; 1347 } 1348 case M_HEADER: 1349 { 1350 bool showHeader = !fHeader->IsMarked(); 1351 fHeader->SetMarked(showHeader); 1352 1353 BMessage message(M_HEADER); 1354 message.AddBool("header", showHeader); 1355 PostMessage(&message, fContentView->TextView()); 1356 break; 1357 } 1358 case M_RAW: 1359 { 1360 bool raw = !(fRaw->IsMarked()); 1361 fRaw->SetMarked(raw); 1362 BMessage message(M_RAW); 1363 message.AddBool("raw", raw); 1364 PostMessage(&message, fContentView->TextView()); 1365 break; 1366 } 1367 case M_SEND_NOW: 1368 case M_SAVE_AS_DRAFT: 1369 Send(msg->what == M_SEND_NOW); 1370 break; 1371 1372 case M_SAVE: 1373 { 1374 const char* address; 1375 if (msg->FindString("address", (const char**)&address) != B_OK) 1376 break; 1377 1378 BVolumeRoster volumeRoster; 1379 BVolume volume; 1380 BQuery query; 1381 BEntry entry; 1382 bool foundEntry = false; 1383 1384 char* arg = (char*)malloc(strlen("META:email=") 1385 + strlen(address) + 1); 1386 sprintf(arg, "META:email=%s", address); 1387 1388 // Search a Person file with this email address 1389 while (volumeRoster.GetNextVolume(&volume) == B_NO_ERROR) { 1390 if (!volume.KnowsQuery()) 1391 continue; 1392 1393 query.SetVolume(&volume); 1394 query.SetPredicate(arg); 1395 query.Fetch(); 1396 1397 if (query.GetNextEntry(&entry) == B_NO_ERROR) { 1398 BMessenger tracker("application/x-vnd.Be-TRAK"); 1399 if (tracker.IsValid()) { 1400 entry_ref ref; 1401 entry.GetRef(&ref); 1402 1403 BMessage open(B_REFS_RECEIVED); 1404 open.AddRef("refs", &ref); 1405 tracker.SendMessage(&open); 1406 foundEntry = true; 1407 break; 1408 } 1409 } 1410 // Try next volume, if any 1411 query.Clear(); 1412 } 1413 1414 if (!foundEntry) { 1415 // None found. 1416 // Ask to open a new Person file with this address pre-filled 1417 1418 status_t result = be_roster->Launch("application/x-person", 1419 1, &arg); 1420 1421 if (result != B_NO_ERROR) { 1422 BAlert* alert = new BAlert("", B_TRANSLATE( 1423 "Sorry, could not find an application that " 1424 "supports the 'Person' data type."), 1425 B_TRANSLATE("OK")); 1426 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1427 alert->Go(); 1428 } 1429 } 1430 free(arg); 1431 break; 1432 } 1433 1434 case M_READ_POS: 1435 PreserveReadingPos(false); 1436 break; 1437 1438 case M_PRINT_SETUP: 1439 PrintSetup(); 1440 break; 1441 1442 case M_PRINT: 1443 Print(); 1444 break; 1445 1446 case M_SELECT: 1447 break; 1448 1449 case M_FIND: 1450 FindWindow::Find(this); 1451 break; 1452 1453 case M_FIND_AGAIN: 1454 FindWindow::FindAgain(this); 1455 break; 1456 1457 case M_QUOTE: 1458 case M_REMOVE_QUOTE: 1459 PostMessage(msg->what, fContentView); 1460 break; 1461 1462 case M_RANDOM_SIG: 1463 { 1464 BList sigList; 1465 BMessage *message; 1466 1467 BVolume volume; 1468 BVolumeRoster().GetBootVolume(&volume); 1469 1470 BQuery query; 1471 query.SetVolume(&volume); 1472 1473 char predicate[128]; 1474 sprintf(predicate, "%s = *", INDEX_SIGNATURE); 1475 query.SetPredicate(predicate); 1476 query.Fetch(); 1477 1478 BEntry entry; 1479 while (query.GetNextEntry(&entry) == B_NO_ERROR) { 1480 BFile file(&entry, O_RDONLY); 1481 if (file.InitCheck() == B_NO_ERROR) { 1482 entry_ref ref; 1483 entry.GetRef(&ref); 1484 1485 message = new BMessage(M_SIGNATURE); 1486 message->AddRef("ref", &ref); 1487 sigList.AddItem(message); 1488 } 1489 } 1490 if (sigList.CountItems() > 0) { 1491 srand(time(0)); 1492 PostMessage((BMessage*)sigList.ItemAt(rand() 1493 % sigList.CountItems())); 1494 1495 for (int32 i = 0; (message = (BMessage*)sigList.ItemAt(i)) 1496 != NULL; i++) 1497 delete message; 1498 } 1499 break; 1500 } 1501 case M_SIGNATURE: 1502 { 1503 BMessage message(*msg); 1504 PostMessage(&message, fContentView); 1505 fSigAdded = true; 1506 break; 1507 } 1508 case M_SIG_MENU: 1509 { 1510 TMenu* menu; 1511 BMenuItem* item; 1512 menu = new TMenu("Add Signature", INDEX_SIGNATURE, M_SIGNATURE, 1513 true); 1514 1515 BPoint where; 1516 if (msg->FindPoint("where", &where) != B_OK) { 1517 BRect bounds = fToolBar->Bounds(); 1518 where = fToolBar->ConvertToScreen(BPoint( 1519 (bounds.right - bounds.left) / 2, 1520 (bounds.bottom - bounds.top) / 2)); 1521 } 1522 1523 if ((item = menu->Go(where, false, true)) != NULL) { 1524 item->SetTarget(this); 1525 (dynamic_cast<BInvoker*>(item))->Invoke(); 1526 } 1527 delete menu; 1528 break; 1529 } 1530 1531 case M_ADD: 1532 if (!fPanel) { 1533 BMessenger me(this); 1534 BMessage msg(REFS_RECEIVED); 1535 fPanel = new BFilePanel(B_OPEN_PANEL, &me, &fOpenFolder, false, 1536 true, &msg); 1537 } else if (!fPanel->Window()->IsHidden()) { 1538 fPanel->Window()->Activate(); 1539 } 1540 1541 if (fPanel->Window()->IsHidden()) 1542 fPanel->Window()->Show(); 1543 break; 1544 1545 case M_REMOVE: 1546 PostMessage(msg->what, fEnclosuresView); 1547 break; 1548 1549 case CHARSET_CHOICE_MADE: 1550 { 1551 int32 charSet; 1552 if (msg->FindInt32("charset", &charSet) != B_OK) 1553 break; 1554 1555 BMessage update(FIELD_CHANGED); 1556 update.AddInt32("bitmask", 0); 1557 // just enable the save button 1558 PostMessage(&update); 1559 1560 if (fIncoming && !fResending) { 1561 // The user wants to see the message they are reading (not 1562 // composing) displayed with a different kind of character set 1563 // for decoding. Reload the whole message and redisplay. For 1564 // messages which are being composed, the character set is 1565 // retrieved from the header view when it is needed. 1566 1567 entry_ref fileRef = *fRef; 1568 OpenMessage(&fileRef, charSet); 1569 } 1570 break; 1571 } 1572 1573 case REFS_RECEIVED: 1574 AddEnclosure(msg); 1575 break; 1576 1577 // 1578 // Navigation Messages 1579 // 1580 case M_UNREAD: 1581 MarkMessageRead(fRef, B_SEEN); 1582 _UpdateReadButton(); 1583 PostMessage(M_NEXTMSG); 1584 break; 1585 case M_READ: 1586 wasReadMsg = true; 1587 _UpdateReadButton(); 1588 msg->what = M_NEXTMSG; 1589 case M_PREVMSG: 1590 case M_NEXTMSG: 1591 { 1592 if (fRef == NULL) 1593 break; 1594 entry_ref orgRef = *fRef; 1595 entry_ref nextRef = *fRef; 1596 if (GetTrackerWindowFile(&nextRef, (msg->what == M_NEXTMSG))) { 1597 TMailWindow* window = static_cast<TMailApp*>(be_app) 1598 ->FindWindow(nextRef); 1599 if (window == NULL) { 1600 BNode node(fRef); 1601 read_flags currentFlag; 1602 if (read_read_attr(node, currentFlag) != B_OK) 1603 currentFlag = B_UNREAD; 1604 if (fAutoMarkRead == true) 1605 MarkMessageRead(fRef, B_READ); 1606 else if (currentFlag != B_READ && !wasReadMsg) 1607 MarkMessageRead(fRef, B_SEEN); 1608 1609 OpenMessage(&nextRef, _CurrentCharacterSet()); 1610 } else { 1611 window->Activate(); 1612 //fSent = true; 1613 PostMessage(B_CLOSE_REQUESTED); 1614 } 1615 1616 SetTrackerSelectionToCurrent(); 1617 } else { 1618 if (wasReadMsg) 1619 PostMessage(B_CLOSE_REQUESTED); 1620 1621 beep(); 1622 } 1623 if (wasReadMsg) 1624 MarkMessageRead(&orgRef, B_READ); 1625 break; 1626 } 1627 1628 case M_SAVE_POSITION: 1629 if (fRef != NULL) 1630 SaveTrackerPosition(fRef); 1631 break; 1632 1633 case RESET_BUTTONS: 1634 fChanged = false; 1635 fFieldState = 0; 1636 if (!fHeaderView->IsToEmpty()) 1637 fFieldState |= FIELD_TO; 1638 if (!fHeaderView->IsSubjectEmpty()) 1639 fFieldState |= FIELD_SUBJECT; 1640 if (!fHeaderView->IsCcEmpty()) 1641 fFieldState |= FIELD_CC; 1642 if (!fHeaderView->IsBccEmpty()) 1643 fFieldState |= FIELD_BCC; 1644 if (fContentView->TextView()->TextLength() != 0) 1645 fFieldState |= FIELD_BODY; 1646 1647 fToolBar->SetActionEnabled(M_SAVE_AS_DRAFT, false); 1648 fToolBar->SetActionEnabled(M_PRINT, fFieldState); 1649 fToolBar->SetActionEnabled(M_SEND_NOW, (fFieldState & FIELD_TO) 1650 || (fFieldState & FIELD_BCC)); 1651 break; 1652 1653 case M_CHECK_SPELLING: 1654 if (gDictCount == 0) 1655 // Give the application time to init and load dictionaries. 1656 snooze (1500000); 1657 if (!gDictCount) { 1658 beep(); 1659 BAlert* alert = new BAlert("", 1660 B_TRANSLATE("Mail couldn't find its dictionary."), 1661 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, 1662 B_OFFSET_SPACING, B_STOP_ALERT); 1663 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1664 alert->Go(); 1665 } else { 1666 fSpelling->SetMarked(!fSpelling->IsMarked()); 1667 fContentView->TextView()->EnableSpellCheck( 1668 fSpelling->IsMarked()); 1669 } 1670 break; 1671 1672 case M_EDIT_QUERIES: 1673 { 1674 BPath path; 1675 if (_GetQueryPath(&path) < B_OK) 1676 break; 1677 1678 // the user used this command, make sure the folder actually 1679 // exists - if it didn't inform the user what to do with it 1680 BEntry entry(path.Path()); 1681 bool showAlert = false; 1682 if (!entry.Exists()) { 1683 showAlert = true; 1684 create_directory(path.Path(), 0777); 1685 } 1686 1687 BEntry folderEntry; 1688 if (folderEntry.SetTo(path.Path()) == B_OK 1689 && folderEntry.Exists()) { 1690 BMessage openFolderCommand(B_REFS_RECEIVED); 1691 BMessenger tracker("application/x-vnd.Be-TRAK"); 1692 1693 entry_ref ref; 1694 folderEntry.GetRef(&ref); 1695 openFolderCommand.AddRef("refs", &ref); 1696 tracker.SendMessage(&openFolderCommand); 1697 } 1698 1699 if (showAlert) { 1700 // just some patience before Tracker pops up the folder 1701 snooze(250000); 1702 BAlert* alert = new BAlert(B_TRANSLATE("helpful message"), 1703 B_TRANSLATE("Put your favorite e-mail queries and query " 1704 "templates in this folder."), B_TRANSLATE("OK"), NULL, NULL, 1705 B_WIDTH_AS_USUAL, B_IDEA_ALERT); 1706 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1707 alert->Go(NULL); 1708 } 1709 1710 break; 1711 } 1712 1713 case B_PATH_MONITOR: 1714 _RebuildQueryMenu(); 1715 break; 1716 1717 default: 1718 BWindow::MessageReceived(msg); 1719 } 1720 } 1721 1722 1723 void 1724 TMailWindow::AddEnclosure(BMessage* msg) 1725 { 1726 if (fEnclosuresView == NULL && !fIncoming) { 1727 BRect r; 1728 r.left = 0; 1729 r.top = fHeaderView->Frame().bottom - 1; 1730 r.right = Frame().Width() + 2; 1731 r.bottom = r.top + ENCLOSURES_HEIGHT; 1732 1733 fEnclosuresView = new TEnclosuresView(r, Frame()); 1734 AddChild(fEnclosuresView, fContentView); 1735 fContentView->ResizeBy(0, -ENCLOSURES_HEIGHT); 1736 fContentView->MoveBy(0, ENCLOSURES_HEIGHT); 1737 } 1738 1739 if (fEnclosuresView == NULL) 1740 return; 1741 1742 if (msg && msg->HasRef("refs")) { 1743 // Add enclosure to view 1744 PostMessage(msg, fEnclosuresView); 1745 1746 fChanged = true; 1747 BEntry entry; 1748 entry_ref ref; 1749 msg->FindRef("refs", &ref); 1750 entry.SetTo(&ref); 1751 entry.GetParent(&entry); 1752 entry.GetRef(&fOpenFolder); 1753 } 1754 } 1755 1756 1757 bool 1758 TMailWindow::QuitRequested() 1759 { 1760 int32 result; 1761 1762 if ((!fIncoming || (fIncoming && fResending)) && fChanged && !fSent 1763 && (!fHeaderView->IsToEmpty() 1764 || !fHeaderView->IsSubjectEmpty() 1765 || !fHeaderView->IsCcEmpty() 1766 || !fHeaderView->IsBccEmpty() 1767 || (fContentView->TextView() != NULL 1768 && strlen(fContentView->TextView()->Text())) 1769 || (fEnclosuresView != NULL 1770 && fEnclosuresView->fList->CountItems()))) { 1771 if (fResending) { 1772 BAlert* alert = new BAlert("", B_TRANSLATE( 1773 "Send this message before closing?"), 1774 B_TRANSLATE("Cancel"), 1775 B_TRANSLATE("Don't send"), 1776 B_TRANSLATE("Send"), 1777 B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT); 1778 alert->SetShortcut(0, B_ESCAPE); 1779 alert->SetShortcut(1, 'd'); 1780 alert->SetShortcut(2, 's'); 1781 result = alert->Go(); 1782 1783 switch (result) { 1784 case 0: // Cancel 1785 return false; 1786 case 1: // Don't send 1787 break; 1788 case 2: // Send 1789 Send(true); 1790 break; 1791 } 1792 } else { 1793 BAlert* alert = new BAlert("", 1794 B_TRANSLATE("Save this message as a draft before closing?"), 1795 B_TRANSLATE("Cancel"), 1796 B_TRANSLATE("Don't save"), 1797 B_TRANSLATE("Save"), 1798 B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT); 1799 alert->SetShortcut(0, B_ESCAPE); 1800 alert->SetShortcut(1, 'd'); 1801 alert->SetShortcut(2, 's'); 1802 result = alert->Go(); 1803 switch (result) { 1804 case 0: // Cancel 1805 return false; 1806 case 1: // Don't Save 1807 break; 1808 case 2: // Save 1809 Send(false); 1810 break; 1811 } 1812 } 1813 } 1814 1815 BMessage message(WINDOW_CLOSED); 1816 message.AddInt32("kind", MAIL_WINDOW); 1817 message.AddPointer("window", this); 1818 be_app->PostMessage(&message); 1819 1820 if (CurrentMessage() && CurrentMessage()->HasString("status")) { 1821 // User explicitly requests a status to set this message to. 1822 if (!CurrentMessage()->HasString("same")) { 1823 const char* status = CurrentMessage()->FindString("status"); 1824 if (status != NULL) { 1825 BNode node(fRef); 1826 if (node.InitCheck() == B_NO_ERROR) { 1827 node.RemoveAttr(B_MAIL_ATTR_STATUS); 1828 WriteAttrString(&node, B_MAIL_ATTR_STATUS, status); 1829 } 1830 } 1831 } 1832 } else if (fRef != NULL && !fKeepStatusOnQuit) { 1833 // ...Otherwise just set the message read 1834 if (fAutoMarkRead == true) 1835 MarkMessageRead(fRef, B_READ); 1836 else { 1837 BNode node(fRef); 1838 read_flags currentFlag; 1839 if (read_read_attr(node, currentFlag) != B_OK) 1840 currentFlag = B_UNREAD; 1841 if (currentFlag == B_UNREAD) 1842 MarkMessageRead(fRef, B_SEEN); 1843 } 1844 } 1845 1846 BPrivate::BPathMonitor::StopWatching(BMessenger(this, this)); 1847 1848 return true; 1849 } 1850 1851 1852 void 1853 TMailWindow::Show() 1854 { 1855 if (Lock()) { 1856 if (!fResending && (fIncoming || fReplying)) { 1857 fContentView->TextView()->MakeFocus(true); 1858 } else { 1859 fHeaderView->ToControl()->MakeFocus(true); 1860 fHeaderView->ToControl()->SelectAll(); 1861 } 1862 Unlock(); 1863 } 1864 BWindow::Show(); 1865 } 1866 1867 1868 void 1869 TMailWindow::Zoom(BPoint /*pos*/, float /*x*/, float /*y*/) 1870 { 1871 float height; 1872 float width; 1873 1874 BRect rect = Frame(); 1875 width = 80 * fApp->ContentFont().StringWidth("M") 1876 + (rect.Width() - fContentView->TextView()->Bounds().Width() + 6); 1877 1878 BScreen screen(this); 1879 BRect screenFrame = screen.Frame(); 1880 if (width > (screenFrame.Width() - 8)) 1881 width = screenFrame.Width() - 8; 1882 1883 height = max_c(fContentView->TextView()->CountLines(), 20) 1884 * fContentView->TextView()->LineHeight(0) 1885 + (rect.Height() - fContentView->TextView()->Bounds().Height()); 1886 if (height > (screenFrame.Height() - 29)) 1887 height = screenFrame.Height() - 29; 1888 1889 rect.right = rect.left + width; 1890 rect.bottom = rect.top + height; 1891 1892 if (abs((int)(Frame().Width() - rect.Width())) < 5 1893 && abs((int)(Frame().Height() - rect.Height())) < 5) { 1894 rect = fZoom; 1895 } else { 1896 fZoom = Frame(); 1897 screenFrame.InsetBy(6, 6); 1898 1899 if (rect.Width() > screenFrame.Width()) 1900 rect.right = rect.left + screenFrame.Width(); 1901 if (rect.Height() > screenFrame.Height()) 1902 rect.bottom = rect.top + screenFrame.Height(); 1903 1904 if (rect.right > screenFrame.right) { 1905 rect.left -= rect.right - screenFrame.right; 1906 rect.right = screenFrame.right; 1907 } 1908 if (rect.bottom > screenFrame.bottom) { 1909 rect.top -= rect.bottom - screenFrame.bottom; 1910 rect.bottom = screenFrame.bottom; 1911 } 1912 if (rect.left < screenFrame.left) { 1913 rect.right += screenFrame.left - rect.left; 1914 rect.left = screenFrame.left; 1915 } 1916 if (rect.top < screenFrame.top) { 1917 rect.bottom += screenFrame.top - rect.top; 1918 rect.top = screenFrame.top; 1919 } 1920 } 1921 1922 ResizeTo(rect.Width(), rect.Height()); 1923 MoveTo(rect.LeftTop()); 1924 } 1925 1926 1927 void 1928 TMailWindow::WindowActivated(bool status) 1929 { 1930 if (status) { 1931 BAutolock locker(sWindowListLock); 1932 sWindowList.RemoveItem(this); 1933 sWindowList.AddItem(this, 0); 1934 } 1935 } 1936 1937 1938 void 1939 TMailWindow::Forward(entry_ref* ref, TMailWindow* window, 1940 bool includeAttachments) 1941 { 1942 BEmailMessage* mail = window->Mail(); 1943 if (mail == NULL) 1944 return; 1945 1946 uint32 useAccountFrom = fApp->UseAccountFrom(); 1947 1948 fMail = mail->ForwardMessage(useAccountFrom == ACCOUNT_FROM_MAIL, 1949 includeAttachments); 1950 1951 BFile file(ref, O_RDONLY); 1952 if (file.InitCheck() < B_NO_ERROR) 1953 return; 1954 1955 fHeaderView->SetSubject(fMail->Subject()); 1956 1957 // set mail account 1958 1959 if (useAccountFrom == ACCOUNT_FROM_MAIL) 1960 fHeaderView->SetAccount(fMail->Account()); 1961 1962 if (fMail->CountComponents() > 1) { 1963 // if there are any enclosures to be added, first add the enclosures 1964 // view to the window 1965 AddEnclosure(NULL); 1966 if (fEnclosuresView) 1967 fEnclosuresView->AddEnclosuresFromMail(fMail); 1968 } 1969 1970 fContentView->TextView()->LoadMessage(fMail, false, NULL); 1971 fChanged = false; 1972 fFieldState = 0; 1973 } 1974 1975 1976 void 1977 TMailWindow::Print() 1978 { 1979 BPrintJob print(Title()); 1980 1981 if (!fApp->HasPrintSettings()) { 1982 if (print.Settings()) { 1983 fApp->SetPrintSettings(print.Settings()); 1984 } else { 1985 PrintSetup(); 1986 if (!fApp->HasPrintSettings()) 1987 return; 1988 } 1989 } 1990 1991 print.SetSettings(new BMessage(fApp->PrintSettings())); 1992 1993 if (print.ConfigJob() == B_OK) { 1994 int32 curPage = 1; 1995 int32 lastLine = 0; 1996 BTextView header_view(print.PrintableRect(), "header", 1997 print.PrintableRect().OffsetByCopy(BPoint( 1998 -print.PrintableRect().left, -print.PrintableRect().top)), 1999 B_FOLLOW_ALL_SIDES); 2000 2001 //---------Init the header fields 2002 #define add_header_field(label, field) { \ 2003 /*header_view.SetFontAndColor(be_bold_font);*/ \ 2004 header_view.Insert(label); \ 2005 header_view.Insert(" "); \ 2006 /*header_view.SetFontAndColor(be_plain_font);*/ \ 2007 header_view.Insert(field); \ 2008 header_view.Insert("\n"); \ 2009 } 2010 2011 add_header_field("Subject:", fHeaderView->Subject()); 2012 add_header_field("To:", fHeaderView->To()); 2013 if (!fHeaderView->IsCcEmpty()) 2014 add_header_field(B_TRANSLATE("Cc:"), fHeaderView->Cc()); 2015 2016 if (!fHeaderView->IsDateEmpty()) 2017 header_view.Insert(fHeaderView->Date()); 2018 2019 int32 maxLine = fContentView->TextView()->CountLines(); 2020 BRect pageRect = print.PrintableRect(); 2021 BRect curPageRect = pageRect; 2022 2023 print.BeginJob(); 2024 float header_height = header_view.TextHeight(0, 2025 header_view.CountLines()); 2026 2027 BRect rect(0, 0, pageRect.Width(), header_height); 2028 BBitmap bmap(rect, B_BITMAP_ACCEPTS_VIEWS, B_RGBA32); 2029 bmap.Lock(); 2030 bmap.AddChild(&header_view); 2031 print.DrawView(&header_view, rect, BPoint(0.0, 0.0)); 2032 HorizontalLine line(BRect(0, 0, pageRect.right, 0)); 2033 bmap.AddChild(&line); 2034 print.DrawView(&line, line.Bounds(), BPoint(0, header_height + 1)); 2035 bmap.Unlock(); 2036 header_height += 5; 2037 2038 do { 2039 int32 lineOffset = fContentView->TextView()->OffsetAt(lastLine); 2040 curPageRect.OffsetTo(0, 2041 fContentView->TextView()->PointAt(lineOffset).y); 2042 2043 int32 fromLine = lastLine; 2044 lastLine = fContentView->TextView()->LineAt( 2045 BPoint(0.0, curPageRect.bottom - ((curPage == 1) 2046 ? header_height : 0))); 2047 2048 float curPageHeight = fContentView->TextView()->TextHeight( 2049 fromLine, lastLine) + (curPage == 1 ? header_height : 0); 2050 2051 if (curPageHeight > pageRect.Height()) { 2052 curPageHeight = fContentView->TextView()->TextHeight( 2053 fromLine, --lastLine) + (curPage == 1 ? header_height : 0); 2054 } 2055 curPageRect.bottom = curPageRect.top + curPageHeight - 1.0; 2056 2057 if (curPage >= print.FirstPage() && curPage <= print.LastPage()) { 2058 print.DrawView(fContentView->TextView(), curPageRect, 2059 BPoint(0.0, curPage == 1 ? header_height : 0.0)); 2060 print.SpoolPage(); 2061 } 2062 2063 curPageRect = pageRect; 2064 lastLine++; 2065 curPage++; 2066 2067 } while (print.CanContinue() && lastLine < maxLine); 2068 2069 print.CommitJob(); 2070 bmap.RemoveChild(&header_view); 2071 bmap.RemoveChild(&line); 2072 } 2073 } 2074 2075 2076 void 2077 TMailWindow::PrintSetup() 2078 { 2079 BPrintJob printJob("mail_print"); 2080 2081 if (fApp->HasPrintSettings()) { 2082 BMessage printSettings = fApp->PrintSettings(); 2083 printJob.SetSettings(new BMessage(printSettings)); 2084 } 2085 2086 if (printJob.ConfigPage() == B_OK) 2087 fApp->SetPrintSettings(printJob.Settings()); 2088 } 2089 2090 2091 void 2092 TMailWindow::SetTo(const char* mailTo, const char* subject, const char* ccTo, 2093 const char* bccTo, const BString* body, BMessage* enclosures) 2094 { 2095 Lock(); 2096 2097 if (mailTo != NULL && mailTo[0]) 2098 fHeaderView->SetTo(mailTo); 2099 if (subject != NULL && subject[0]) 2100 fHeaderView->SetSubject(subject); 2101 if (ccTo != NULL && ccTo[0]) 2102 fHeaderView->SetCc(ccTo); 2103 if (bccTo != NULL && bccTo[0]) 2104 fHeaderView->SetBcc(bccTo); 2105 2106 if (body != NULL && body->Length()) { 2107 fContentView->TextView()->SetText(body->String(), body->Length()); 2108 fContentView->TextView()->GoToLine(0); 2109 } 2110 2111 if (enclosures && enclosures->HasRef("refs")) 2112 AddEnclosure(enclosures); 2113 2114 Unlock(); 2115 } 2116 2117 2118 void 2119 TMailWindow::CopyMessage(entry_ref* ref, TMailWindow* src) 2120 { 2121 BNode file(ref); 2122 if (file.InitCheck() == B_OK) { 2123 BString string; 2124 if (file.ReadAttrString(B_MAIL_ATTR_TO, &string) == B_OK) 2125 fHeaderView->SetTo(string); 2126 2127 if (file.ReadAttrString(B_MAIL_ATTR_SUBJECT, &string) == B_OK) 2128 fHeaderView->SetSubject(string); 2129 2130 if (file.ReadAttrString(B_MAIL_ATTR_CC, &string) == B_OK) 2131 fHeaderView->SetCc(string); 2132 } 2133 2134 TTextView* text = src->fContentView->TextView(); 2135 text_run_array* style = text->RunArray(0, text->TextLength()); 2136 2137 fContentView->TextView()->SetText(text->Text(), text->TextLength(), style); 2138 2139 free(style); 2140 } 2141 2142 2143 void 2144 TMailWindow::Reply(entry_ref* ref, TMailWindow* window, uint32 type) 2145 { 2146 fRepliedMail = *ref; 2147 SetOriginatingWindow(window); 2148 2149 BEmailMessage* mail = window->Mail(); 2150 if (mail == NULL) 2151 return; 2152 2153 if (type == M_REPLY_ALL) 2154 type = B_MAIL_REPLY_TO_ALL; 2155 else if (type == M_REPLY_TO_SENDER) 2156 type = B_MAIL_REPLY_TO_SENDER; 2157 else 2158 type = B_MAIL_REPLY_TO; 2159 2160 uint32 useAccountFrom = fApp->UseAccountFrom(); 2161 2162 fMail = mail->ReplyMessage(mail_reply_to_mode(type), 2163 useAccountFrom == ACCOUNT_FROM_MAIL, QUOTE); 2164 2165 // set header fields 2166 fHeaderView->SetTo(fMail->To()); 2167 fHeaderView->SetCc(fMail->CC()); 2168 fHeaderView->SetSubject(fMail->Subject()); 2169 2170 int32 accountID; 2171 BFile file(window->fRef, B_READ_ONLY); 2172 if (file.ReadAttr("MAIL:reply_with", B_INT32_TYPE, 0, &accountID, 2173 sizeof(int32)) != B_OK) 2174 accountID = -1; 2175 2176 // set mail account 2177 2178 if ((useAccountFrom == ACCOUNT_FROM_MAIL) || (accountID > -1)) { 2179 if (useAccountFrom == ACCOUNT_FROM_MAIL) 2180 fHeaderView->SetAccount(fMail->Account()); 2181 else 2182 fHeaderView->SetAccount(accountID); 2183 } 2184 2185 // create preamble string 2186 2187 BString preamble = fApp->ReplyPreamble(); 2188 2189 BString name; 2190 mail->GetName(&name); 2191 if (name.Length() <= 0) 2192 name = B_TRANSLATE("(Name unavailable)"); 2193 2194 BString address(mail->From()); 2195 if (address.Length() <= 0) 2196 address = B_TRANSLATE("(Address unavailable)"); 2197 2198 BString date(mail->HeaderField("Date")); 2199 if (date.Length() <= 0) 2200 date = B_TRANSLATE("(Date unavailable)"); 2201 2202 preamble.ReplaceAll("%n", name); 2203 preamble.ReplaceAll("%e", address); 2204 preamble.ReplaceAll("%d", date); 2205 preamble.ReplaceAll("\\n", "\n"); 2206 2207 // insert (if selection) or load (if whole mail) message text into text view 2208 2209 int32 finish, start; 2210 window->fContentView->TextView()->GetSelection(&start, &finish); 2211 if (start != finish) { 2212 char* text = (char*)malloc(finish - start + 1); 2213 if (text == NULL) 2214 return; 2215 2216 window->fContentView->TextView()->GetText(start, finish - start, text); 2217 if (text[strlen(text) - 1] != '\n') { 2218 text[strlen(text)] = '\n'; 2219 finish++; 2220 } 2221 fContentView->TextView()->SetText(text, finish - start); 2222 free(text); 2223 2224 finish = fContentView->TextView()->CountLines(); 2225 for (int32 loop = 0; loop < finish; loop++) { 2226 fContentView->TextView()->GoToLine(loop); 2227 fContentView->TextView()->Insert((const char*)QUOTE); 2228 } 2229 2230 if (fApp->ColoredQuotes()) { 2231 const BFont* font = fContentView->TextView()->Font(); 2232 int32 length = fContentView->TextView()->TextLength(); 2233 2234 TextRunArray style(length / 8 + 8); 2235 2236 FillInQuoteTextRuns(fContentView->TextView(), NULL, 2237 fContentView->TextView()->Text(), length, font, &style.Array(), 2238 style.MaxEntries()); 2239 2240 fContentView->TextView()->SetRunArray(0, length, &style.Array()); 2241 } 2242 2243 fContentView->TextView()->GoToLine(0); 2244 if (preamble.Length() > 0) 2245 fContentView->TextView()->Insert(preamble); 2246 } else { 2247 fContentView->TextView()->LoadMessage(mail, true, preamble); 2248 } 2249 2250 fReplying = true; 2251 } 2252 2253 2254 status_t 2255 TMailWindow::Send(bool now) 2256 { 2257 if (!now) { 2258 status_t status = SaveAsDraft(); 2259 if (status != B_OK) { 2260 beep(); 2261 BAlert* alert = new BAlert("", B_TRANSLATE("E-mail draft could " 2262 "not be saved!"), B_TRANSLATE("OK")); 2263 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2264 alert->Go(); 2265 } 2266 return status; 2267 } 2268 2269 uint32 characterSetToUse = _CurrentCharacterSet(); 2270 mail_encoding encodingForBody = quoted_printable; 2271 mail_encoding encodingForHeaders = quoted_printable; 2272 2273 // Set up the encoding to use for converting binary to printable ASCII. 2274 // Normally this will be quoted printable, but for some old software, 2275 // particularly Japanese stuff, they only understand base64. They also 2276 // prefer it for the smaller size. Later on this will be reduced to 7bit 2277 // if the encoded text is just 7bit characters. 2278 if (characterSetToUse == B_SJIS_CONVERSION 2279 || characterSetToUse == B_EUC_CONVERSION) 2280 encodingForBody = base64; 2281 else if (characterSetToUse == B_JIS_CONVERSION 2282 || characterSetToUse == B_MAIL_US_ASCII_CONVERSION 2283 || characterSetToUse == B_ISO1_CONVERSION 2284 || characterSetToUse == B_EUC_KR_CONVERSION) 2285 encodingForBody = eight_bit; 2286 2287 // Using quoted printable headers on almost completely non-ASCII Japanese 2288 // is a waste of time. Besides, some stupid cell phone services need 2289 // base64 in the headers. 2290 if (characterSetToUse == B_SJIS_CONVERSION 2291 || characterSetToUse == B_EUC_CONVERSION 2292 || characterSetToUse == B_JIS_CONVERSION 2293 || characterSetToUse == B_EUC_KR_CONVERSION) 2294 encodingForHeaders = base64; 2295 2296 // Count the number of characters in the message body which aren't in the 2297 // currently selected character set. Also see if the resulting encoded 2298 // text can safely use 7 bit characters. 2299 if (fContentView->TextView()->TextLength() > 0) { 2300 // First do a trial encoding with the user's character set. 2301 int32 converterState = 0; 2302 int32 originalLength; 2303 BString tempString; 2304 int32 tempStringLength; 2305 char* tempStringPntr; 2306 originalLength = fContentView->TextView()->TextLength(); 2307 tempStringLength = originalLength * 6; 2308 // Some character sets bloat up on escape codes 2309 tempStringPntr = tempString.LockBuffer (tempStringLength); 2310 if (tempStringPntr != NULL && mail_convert_from_utf8(characterSetToUse, 2311 fContentView->TextView()->Text(), &originalLength, 2312 tempStringPntr, &tempStringLength, &converterState, 2313 0x1A /* used for unknown characters */) == B_OK) { 2314 // Check for any characters which don't fit in a 7 bit encoding. 2315 int i; 2316 bool has8Bit = false; 2317 for (i = 0; i < tempStringLength; i++) { 2318 if (tempString[i] == 0 || (tempString[i] & 0x80)) { 2319 has8Bit = true; 2320 break; 2321 } 2322 } 2323 if (!has8Bit) 2324 encodingForBody = seven_bit; 2325 tempString.UnlockBuffer (tempStringLength); 2326 2327 // Count up the number of unencoded characters and warn the user 2328 if (fApp->WarnAboutUnencodableCharacters()) { 2329 // TODO: ideally, the encoding should be silently changed to 2330 // one that can express this character 2331 int32 offset = 0; 2332 int count = 0; 2333 while (offset >= 0) { 2334 offset = tempString.FindFirst (0x1A, offset); 2335 if (offset >= 0) { 2336 count++; 2337 offset++; 2338 // Don't get stuck finding the same character again. 2339 } 2340 } 2341 if (count > 0) { 2342 int32 userAnswer; 2343 BString messageString; 2344 BString countString; 2345 countString << count; 2346 messageString << B_TRANSLATE("Your main text contains %ld" 2347 " unencodable characters. Perhaps a different " 2348 "character set would work better? Hit Send to send it " 2349 "anyway " 2350 "(a substitute character will be used in place of " 2351 "the unencodable ones), or choose Cancel to go back " 2352 "and try fixing it up."); 2353 messageString.ReplaceFirst("%ld", countString); 2354 BAlert* alert = new BAlert("Question", messageString.String(), 2355 B_TRANSLATE("Send"), 2356 B_TRANSLATE("Cancel"), 2357 NULL, B_WIDTH_AS_USUAL, B_OFFSET_SPACING, 2358 B_WARNING_ALERT); 2359 alert->SetShortcut(1, B_ESCAPE); 2360 userAnswer = alert->Go(); 2361 2362 if (userAnswer == 1) { 2363 // Cancel was picked. 2364 return -1; 2365 } 2366 } 2367 } 2368 } 2369 } 2370 2371 Hide(); 2372 // depending on the system (and I/O) load, this could take a while 2373 // but the user shouldn't be left waiting 2374 2375 status_t result; 2376 2377 if (fResending) { 2378 BFile file(fRef, O_RDONLY); 2379 result = file.InitCheck(); 2380 if (result == B_OK) { 2381 BEmailMessage mail(&file); 2382 mail.SetTo(fHeaderView->To(), characterSetToUse, 2383 encodingForHeaders); 2384 2385 if (fHeaderView->AccountID() != ~0L) 2386 mail.SendViaAccount(fHeaderView->AccountID()); 2387 2388 result = mail.Send(now); 2389 } 2390 } else { 2391 if (fMail == NULL) 2392 // the mail will be deleted when the window is closed 2393 fMail = new BEmailMessage; 2394 2395 // Had an embarrassing bug where replying to a message and clearing the 2396 // CC field meant that it got sent out anyway, so pass in empty strings 2397 // when changing the header to force it to remove the header. 2398 2399 fMail->SetTo(fHeaderView->To(), characterSetToUse, encodingForHeaders); 2400 fMail->SetSubject(fHeaderView->Subject(), characterSetToUse, 2401 encodingForHeaders); 2402 fMail->SetCC(fHeaderView->Cc(), characterSetToUse, encodingForHeaders); 2403 fMail->SetBCC(fHeaderView->Bcc()); 2404 2405 //--- Add X-Mailer field 2406 { 2407 // get app version 2408 version_info info; 2409 memset(&info, 0, sizeof(version_info)); 2410 2411 app_info appInfo; 2412 if (be_app->GetAppInfo(&appInfo) == B_OK) { 2413 BFile file(&appInfo.ref, B_READ_ONLY); 2414 if (file.InitCheck() == B_OK) { 2415 BAppFileInfo appFileInfo(&file); 2416 if (appFileInfo.InitCheck() == B_OK) 2417 appFileInfo.GetVersionInfo(&info, B_APP_VERSION_KIND); 2418 } 2419 } 2420 2421 char versionString[255]; 2422 sprintf(versionString, 2423 "Mail/Haiku %" B_PRIu32 ".%" B_PRIu32 ".%" B_PRIu32, 2424 info.major, info.middle, info.minor); 2425 fMail->SetHeaderField("X-Mailer", versionString); 2426 } 2427 2428 /****/ 2429 2430 // the content text is always added to make sure there is a mail body 2431 fMail->SetBodyTextTo(""); 2432 fContentView->TextView()->AddAsContent(fMail, fApp->WrapMode(), 2433 characterSetToUse, encodingForBody); 2434 2435 if (fEnclosuresView != NULL) { 2436 TListItem* item; 2437 int32 index = 0; 2438 while ((item = (TListItem*)fEnclosuresView->fList->ItemAt(index++)) 2439 != NULL) { 2440 if (item->Component()) 2441 continue; 2442 2443 // leave out missing enclosures 2444 BEntry entry(item->Ref()); 2445 if (!entry.Exists()) 2446 continue; 2447 2448 fMail->Attach(item->Ref(), fApp->AttachAttributes()); 2449 } 2450 } 2451 if (fHeaderView->AccountID() != ~0L) 2452 fMail->SendViaAccount(fHeaderView->AccountID()); 2453 2454 result = fMail->Send(now); 2455 2456 if (fReplying) { 2457 // Set status of the replied mail 2458 2459 BNode node(&fRepliedMail); 2460 if (node.InitCheck() >= B_OK) { 2461 if (fOriginatingWindow) { 2462 BMessage msg(M_SAVE_POSITION), reply; 2463 fOriginatingWindow->SendMessage(&msg, &reply); 2464 } 2465 WriteAttrString(&node, B_MAIL_ATTR_STATUS, "Replied"); 2466 } 2467 } 2468 } 2469 2470 bool close = false; 2471 BString errorMessage; 2472 2473 switch (result) { 2474 case B_OK: 2475 close = true; 2476 fSent = true; 2477 2478 // If it's a draft, remove the draft file 2479 if (fDraft) { 2480 BEntry entry(fRef); 2481 entry.Remove(); 2482 } 2483 break; 2484 2485 case B_MAIL_NO_DAEMON: 2486 { 2487 close = true; 2488 fSent = true; 2489 2490 BAlert* alert = new BAlert("no daemon", 2491 B_TRANSLATE("The mail_daemon is not running. The message is " 2492 "queued and will be sent when the mail_daemon is started."), 2493 B_TRANSLATE("Start now"), B_TRANSLATE("OK")); 2494 alert->SetShortcut(1, B_ESCAPE); 2495 int32 start = alert->Go(); 2496 2497 if (start == 0) { 2498 BMailDaemon daemon; 2499 result = daemon.Launch(); 2500 if (result == B_OK) { 2501 daemon.SendQueuedMail(); 2502 } else { 2503 errorMessage 2504 << B_TRANSLATE("The mail_daemon could not be " 2505 "started:\n\t") 2506 << strerror(result); 2507 } 2508 } 2509 break; 2510 } 2511 2512 // case B_MAIL_UNKNOWN_HOST: 2513 // case B_MAIL_ACCESS_ERROR: 2514 // sprintf(errorMessage, 2515 // "An error occurred trying to connect with the SMTP " 2516 // "host. Check your SMTP host name."); 2517 // break; 2518 // 2519 // case B_MAIL_NO_RECIPIENT: 2520 // sprintf(errorMessage, 2521 // "You must have either a \"To\" or \"Bcc\" recipient."); 2522 // break; 2523 2524 default: 2525 errorMessage << "An error occurred trying to send mail:\n\t" 2526 << strerror(result); 2527 break; 2528 } 2529 2530 if (result != B_NO_ERROR && result != B_MAIL_NO_DAEMON) { 2531 beep(); 2532 BAlert* alert = new BAlert("", errorMessage.String(), 2533 B_TRANSLATE("OK")); 2534 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2535 alert->Go(); 2536 } 2537 if (close) { 2538 PostMessage(B_QUIT_REQUESTED); 2539 } else { 2540 // The window was hidden earlier 2541 Show(); 2542 } 2543 2544 return result; 2545 } 2546 2547 2548 status_t 2549 TMailWindow::SaveAsDraft() 2550 { 2551 BPath draftPath; 2552 BDirectory dir; 2553 BFile draft; 2554 uint32 flags = 0; 2555 2556 if (fDraft) { 2557 status_t status = draft.SetTo(fRef, 2558 B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 2559 if (status != B_OK) 2560 return status; 2561 } else { 2562 // Get the user home directory 2563 status_t status = find_directory(B_USER_DIRECTORY, &draftPath); 2564 if (status != B_OK) 2565 return status; 2566 2567 // Append the relative path of the draft directory 2568 draftPath.Append(kDraftPath); 2569 2570 // Create the file 2571 status = dir.SetTo(draftPath.Path()); 2572 switch (status) { 2573 // Create the directory if it does not exist 2574 case B_ENTRY_NOT_FOUND: 2575 if ((status = dir.CreateDirectory(draftPath.Path(), &dir)) 2576 != B_OK) 2577 return status; 2578 case B_OK: 2579 { 2580 char fileName[B_FILE_NAME_LENGTH]; 2581 // save as some version of the message's subject 2582 if (fHeaderView->IsSubjectEmpty()) { 2583 strlcpy(fileName, B_TRANSLATE("Untitled"), 2584 sizeof(fileName)); 2585 } else { 2586 strlcpy(fileName, fHeaderView->Subject(), sizeof(fileName)); 2587 } 2588 2589 uint32 originalLength = strlen(fileName); 2590 2591 // convert /, \ and : to - 2592 for (char* bad = fileName; (bad = strchr(bad, '/')) != NULL; 2593 ++bad) { 2594 *bad = '-'; 2595 } 2596 for (char* bad = fileName; (bad = strchr(bad, '\\')) != NULL; 2597 ++bad) { 2598 *bad = '-'; 2599 } 2600 for (char* bad = fileName; (bad = strchr(bad, ':')) != NULL; 2601 ++bad) { 2602 *bad = '-'; 2603 } 2604 2605 // Create the file; if the name exists, find a unique name 2606 flags = B_WRITE_ONLY | B_CREATE_FILE | B_FAIL_IF_EXISTS; 2607 int32 i = 1; 2608 do { 2609 status = draft.SetTo(&dir, fileName, flags); 2610 if (status == B_OK) 2611 break; 2612 char appendix[B_FILE_NAME_LENGTH]; 2613 sprintf(appendix, " %" B_PRId32, i++); 2614 int32 pos = min_c(sizeof(fileName) - strlen(appendix), 2615 originalLength); 2616 sprintf(fileName + pos, "%s", appendix); 2617 } while (status == B_FILE_EXISTS); 2618 if (status != B_OK) 2619 return status; 2620 2621 // Cache the ref 2622 if (fRef == NULL) 2623 fRef = new entry_ref; 2624 BEntry entry(&dir, fileName); 2625 entry.GetRef(fRef); 2626 break; 2627 } 2628 default: 2629 return status; 2630 } 2631 } 2632 2633 // Write the content of the message 2634 draft.Write(fContentView->TextView()->Text(), 2635 fContentView->TextView()->TextLength()); 2636 2637 // Add the header stuff as attributes 2638 WriteAttrString(&draft, B_MAIL_ATTR_NAME, fHeaderView->To()); 2639 WriteAttrString(&draft, B_MAIL_ATTR_TO, fHeaderView->To()); 2640 WriteAttrString(&draft, B_MAIL_ATTR_SUBJECT, fHeaderView->Subject()); 2641 if (!fHeaderView->IsCcEmpty()) 2642 WriteAttrString(&draft, B_MAIL_ATTR_CC, fHeaderView->Cc()); 2643 if (!fHeaderView->IsBccEmpty()) 2644 WriteAttrString(&draft, B_MAIL_ATTR_BCC, fHeaderView->Bcc()); 2645 2646 // Add account 2647 if (fHeaderView->AccountName() != NULL) { 2648 WriteAttrString(&draft, B_MAIL_ATTR_ACCOUNT, 2649 fHeaderView->AccountName()); 2650 } 2651 2652 // Add encoding 2653 BMenuItem* menuItem = fEncodingMenu->FindMarked(); 2654 if (menuItem != NULL) 2655 WriteAttrString(&draft, "MAIL:encoding", menuItem->Label()); 2656 2657 // Add the draft attribute for indexing 2658 uint32 draftAttr = true; 2659 draft.WriteAttr("MAIL:draft", B_INT32_TYPE, 0, &draftAttr, sizeof(uint32)); 2660 2661 // Add Attachment paths in attribute 2662 if (fEnclosuresView != NULL) { 2663 TListItem* item; 2664 BString pathStr; 2665 2666 for (int32 i = 0; (item = (TListItem*)fEnclosuresView->fList->ItemAt(i)) 2667 != NULL; i++) { 2668 if (i > 0) 2669 pathStr.Append(":"); 2670 2671 BEntry entry(item->Ref(), true); 2672 if (!entry.Exists()) 2673 continue; 2674 2675 BPath path; 2676 entry.GetPath(&path); 2677 pathStr.Append(path.Path()); 2678 } 2679 if (pathStr.Length()) 2680 draft.WriteAttrString("MAIL:attachments", &pathStr); 2681 } 2682 2683 // Set the MIME Type of the file 2684 BNodeInfo info(&draft); 2685 info.SetType(kDraftType); 2686 2687 fDraft = true; 2688 fChanged = false; 2689 2690 fToolBar->SetActionEnabled(M_SAVE_AS_DRAFT, false); 2691 2692 return B_OK; 2693 } 2694 2695 2696 status_t 2697 TMailWindow::TrainMessageAs(const char* commandWord) 2698 { 2699 status_t errorCode = -1; 2700 BEntry fileEntry; 2701 BPath filePath; 2702 BMessage replyMessage; 2703 BMessage scriptingMessage; 2704 team_id serverTeam; 2705 2706 if (fRef == NULL) 2707 goto ErrorExit; // Need to have a real file and name. 2708 errorCode = fileEntry.SetTo(fRef, true); 2709 if (errorCode != B_OK) 2710 goto ErrorExit; 2711 errorCode = fileEntry.GetPath(&filePath); 2712 if (errorCode != B_OK) 2713 goto ErrorExit; 2714 fileEntry.Unset(); 2715 2716 // Get a connection to the spam database server. Launch if needed. 2717 2718 if (!fMessengerToSpamServer.IsValid()) { 2719 // Make sure the server is running. 2720 if (!be_roster->IsRunning (kSpamServerSignature)) { 2721 errorCode = be_roster->Launch (kSpamServerSignature); 2722 if (errorCode != B_OK) { 2723 BPath path; 2724 entry_ref ref; 2725 directory_which places[] = {B_SYSTEM_NONPACKAGED_BIN_DIRECTORY, 2726 B_SYSTEM_BIN_DIRECTORY}; 2727 for (int32 i = 0; i < 2; i++) { 2728 find_directory(places[i],&path); 2729 path.Append("spamdbm"); 2730 if (!BEntry(path.Path()).Exists()) 2731 continue; 2732 get_ref_for_path(path.Path(),&ref); 2733 2734 errorCode = be_roster->Launch(&ref); 2735 if (errorCode == B_OK) 2736 break; 2737 } 2738 if (errorCode != B_OK) 2739 goto ErrorExit; 2740 } 2741 } 2742 2743 // Set up the messenger to the database server. 2744 errorCode = B_SERVER_NOT_FOUND; 2745 serverTeam = be_roster->TeamFor(kSpamServerSignature); 2746 if (serverTeam < 0) 2747 goto ErrorExit; 2748 2749 fMessengerToSpamServer = BMessenger (kSpamServerSignature, serverTeam, 2750 &errorCode); 2751 2752 if (!fMessengerToSpamServer.IsValid()) 2753 goto ErrorExit; 2754 } 2755 2756 // Ask the server to train on the message. Give it the command word and 2757 // the absolute path name to use. 2758 2759 scriptingMessage.MakeEmpty(); 2760 scriptingMessage.what = B_SET_PROPERTY; 2761 scriptingMessage.AddSpecifier(commandWord); 2762 errorCode = scriptingMessage.AddData("data", B_STRING_TYPE, 2763 filePath.Path(), strlen(filePath.Path()) + 1, false); 2764 if (errorCode != B_OK) 2765 goto ErrorExit; 2766 replyMessage.MakeEmpty(); 2767 errorCode = fMessengerToSpamServer.SendMessage(&scriptingMessage, 2768 &replyMessage); 2769 if (errorCode != B_OK 2770 || replyMessage.FindInt32("error", &errorCode) != B_OK 2771 || errorCode != B_OK) 2772 goto ErrorExit; // Classification failed in one of many ways. 2773 2774 SetTitleForMessage(); 2775 // Update window title to show new spam classification. 2776 return B_OK; 2777 2778 ErrorExit: 2779 beep(); 2780 char errorString[1500]; 2781 snprintf(errorString, sizeof(errorString), "Unable to train the message " 2782 "file \"%s\" as %s. Possibly useful error code: %s (%" B_PRId32 ").", 2783 filePath.Path(), commandWord, strerror(errorCode), errorCode); 2784 BAlert* alert = new BAlert("", errorString, B_TRANSLATE("OK")); 2785 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2786 alert->Go(); 2787 2788 return errorCode; 2789 } 2790 2791 2792 void 2793 TMailWindow::SetTitleForMessage() 2794 { 2795 // Figure out the title of this message and set the title bar 2796 BString title = B_TRANSLATE_SYSTEM_NAME("Mail"); 2797 2798 if (fIncoming) { 2799 if (fMail->GetName(&title) == B_OK) 2800 title << ": \"" << fMail->Subject() << "\""; 2801 else 2802 title = fMail->Subject(); 2803 2804 if (fDownloading) 2805 title.Prepend("Downloading: "); 2806 2807 if (fApp->ShowSpamGUI() && fRef != NULL) { 2808 BString classification; 2809 BNode node(fRef); 2810 char numberString[30]; 2811 BString oldTitle(title); 2812 float spamRatio; 2813 if (node.InitCheck() != B_OK || node.ReadAttrString( 2814 "MAIL:classification", &classification) != B_OK) 2815 classification = "Unrated"; 2816 if (classification != "Spam" && classification != "Genuine") { 2817 // Uncertain, Unrated and other unknown classes, show the ratio. 2818 if (node.InitCheck() == B_OK && node.ReadAttr("MAIL:ratio_spam", 2819 B_FLOAT_TYPE, 0, &spamRatio, sizeof(spamRatio)) 2820 == sizeof(spamRatio)) { 2821 sprintf(numberString, "%.4f", spamRatio); 2822 classification << " " << numberString; 2823 } 2824 } 2825 title = ""; 2826 title << "[" << classification << "] " << oldTitle; 2827 } 2828 } 2829 SetTitle(title); 2830 } 2831 2832 2833 /*! Open *another* message in the existing mail window. Some code here is 2834 duplicated from various constructors. 2835 TODO: The duplicated code should be moved to a private initializer method 2836 */ 2837 status_t 2838 TMailWindow::OpenMessage(const entry_ref* ref, uint32 characterSetForDecoding) 2839 { 2840 if (ref == NULL) 2841 return B_ERROR; 2842 2843 // Set some references to the email file 2844 delete fRef; 2845 fRef = new entry_ref(*ref); 2846 2847 fPrevTrackerPositionSaved = false; 2848 fNextTrackerPositionSaved = false; 2849 2850 fContentView->TextView()->StopLoad(); 2851 delete fMail; 2852 fMail = NULL; 2853 2854 BFile file(fRef, B_READ_ONLY); 2855 status_t err = file.InitCheck(); 2856 if (err != B_OK) 2857 return err; 2858 2859 char mimeType[256]; 2860 BNodeInfo fileInfo(&file); 2861 fileInfo.GetType(mimeType); 2862 2863 if (strcmp(mimeType, B_PARTIAL_MAIL_TYPE) == 0) { 2864 BMessenger listener(this); 2865 BMailDaemon().FetchBody(*ref, &listener); 2866 fileInfo.GetType(mimeType); 2867 _SetDownloading(true); 2868 } else 2869 _SetDownloading(false); 2870 2871 // Check if it's a draft file, which contains only the text, and has the 2872 // from, to, bcc, attachments listed as attributes. 2873 if (strcmp(kDraftType, mimeType) == 0) { 2874 BNode node(fRef); 2875 off_t size; 2876 BString string; 2877 2878 fMail = new BEmailMessage; // Not really used much, but still needed. 2879 2880 // Load the raw UTF-8 text from the file. 2881 file.GetSize(&size); 2882 fContentView->TextView()->SetText(&file, 0, size); 2883 2884 // Restore Fields from attributes 2885 if (node.ReadAttrString(B_MAIL_ATTR_TO, &string) == B_OK) 2886 fHeaderView->SetTo(string); 2887 if (node.ReadAttrString(B_MAIL_ATTR_SUBJECT, &string) == B_OK) 2888 fHeaderView->SetSubject(string); 2889 if (node.ReadAttrString(B_MAIL_ATTR_CC, &string) == B_OK) 2890 fHeaderView->SetCc(string); 2891 if (node.ReadAttrString(B_MAIL_ATTR_BCC, &string) == B_OK) 2892 fHeaderView->SetBcc(string); 2893 2894 // Restore account 2895 if (node.ReadAttrString(B_MAIL_ATTR_ACCOUNT, &string) == B_OK) 2896 fHeaderView->SetAccount(string); 2897 2898 // Restore encoding 2899 if (node.ReadAttrString("MAIL:encoding", &string) == B_OK) { 2900 BMenuItem* encodingItem = fEncodingMenu->FindItem(string.String()); 2901 if (encodingItem != NULL) 2902 encodingItem->SetMarked(true); 2903 } 2904 2905 // Restore attachments 2906 if (node.ReadAttrString("MAIL:attachments", &string) == B_OK) { 2907 BMessage msg(REFS_RECEIVED); 2908 entry_ref enc_ref; 2909 2910 char* s = strtok((char*)string.String(), ":"); 2911 while (s != NULL) { 2912 BEntry entry(s, true); 2913 if (entry.Exists()) { 2914 entry.GetRef(&enc_ref); 2915 msg.AddRef("refs", &enc_ref); 2916 } 2917 s = strtok(NULL, ":"); 2918 } 2919 AddEnclosure(&msg); 2920 } 2921 2922 // restore the reading position if available 2923 PostMessage(M_READ_POS); 2924 2925 PostMessage(RESET_BUTTONS); 2926 fIncoming = false; 2927 fDraft = true; 2928 } else { 2929 // A real mail message, parse its headers to get from, to, etc. 2930 fMail = new BEmailMessage(fRef, characterSetForDecoding); 2931 fIncoming = true; 2932 fHeaderView->SetFromMessage(fMail); 2933 } 2934 2935 err = fMail->InitCheck(); 2936 if (err < B_OK) { 2937 delete fMail; 2938 fMail = NULL; 2939 return err; 2940 } 2941 2942 SetTitleForMessage(); 2943 2944 if (fIncoming) { 2945 // Put the addresses in the 'Save Address' Menu 2946 BMenuItem* item; 2947 while ((item = fSaveAddrMenu->RemoveItem((int32)0)) != NULL) 2948 delete item; 2949 2950 // create the list of addresses 2951 2952 BList addressList; 2953 get_address_list(addressList, fMail->To(), extract_address); 2954 get_address_list(addressList, fMail->CC(), extract_address); 2955 get_address_list(addressList, fMail->From(), extract_address); 2956 get_address_list(addressList, fMail->ReplyTo(), extract_address); 2957 2958 BMessage* msg; 2959 2960 for (int32 i = addressList.CountItems(); i-- > 0;) { 2961 char* address = (char*)addressList.RemoveItem((int32)0); 2962 2963 // insert the new address in alphabetical order 2964 int32 index = 0; 2965 while ((item = fSaveAddrMenu->ItemAt(index)) != NULL) { 2966 if (!strcmp(address, item->Label())) { 2967 // item already in list 2968 goto skip; 2969 } 2970 2971 if (strcmp(address, item->Label()) < 0) 2972 break; 2973 2974 index++; 2975 } 2976 2977 msg = new BMessage(M_SAVE); 2978 msg->AddString("address", address); 2979 fSaveAddrMenu->AddItem(new BMenuItem(address, msg), index); 2980 2981 skip: 2982 free(address); 2983 } 2984 2985 // Clear out existing contents of text view. 2986 fContentView->TextView()->SetText("", (int32)0); 2987 2988 fContentView->TextView()->LoadMessage(fMail, false, NULL); 2989 2990 if (fApp->ShowToolBar()) 2991 _UpdateReadButton(); 2992 } 2993 2994 return B_OK; 2995 } 2996 2997 2998 TMailWindow* 2999 TMailWindow::FrontmostWindow() 3000 { 3001 BAutolock locker(sWindowListLock); 3002 if (sWindowList.CountItems() > 0) 3003 return (TMailWindow*)sWindowList.ItemAt(0); 3004 3005 return NULL; 3006 } 3007 3008 3009 // #pragma mark - 3010 3011 3012 status_t 3013 TMailWindow::_GetQueryPath(BPath* queryPath) const 3014 { 3015 // get the user home directory and from there the query folder 3016 status_t ret = find_directory(B_USER_DIRECTORY, queryPath); 3017 if (ret == B_OK) 3018 ret = queryPath->Append(kQueriesDirectory); 3019 3020 return ret; 3021 } 3022 3023 3024 void 3025 TMailWindow::_RebuildQueryMenu(bool firstTime) 3026 { 3027 while (fQueryMenu->ItemAt(0)) { 3028 BMenuItem* item = fQueryMenu->RemoveItem((int32)0); 3029 delete item; 3030 } 3031 3032 fQueryMenu->AddItem(new BMenuItem(B_TRANSLATE("Edit queries" 3033 B_UTF8_ELLIPSIS), 3034 new BMessage(M_EDIT_QUERIES), 'E', B_SHIFT_KEY)); 3035 3036 bool queryItemsAdded = false; 3037 3038 BPath queryPath; 3039 if (_GetQueryPath(&queryPath) < B_OK) 3040 return; 3041 3042 BDirectory queryDir(queryPath.Path()); 3043 3044 if (firstTime) { 3045 BPrivate::BPathMonitor::StartWatching(queryPath.Path(), 3046 B_WATCH_RECURSIVELY, BMessenger(this, this)); 3047 } 3048 3049 // If we find the named query, add it to the menu. 3050 BEntry entry; 3051 while (queryDir.GetNextEntry(&entry) == B_OK) { 3052 char name[B_FILE_NAME_LENGTH + 1]; 3053 entry.GetName(name); 3054 3055 char* queryString = _BuildQueryString(&entry); 3056 if (queryString == NULL) 3057 continue; 3058 3059 queryItemsAdded = true; 3060 3061 QueryMenu* queryMenu = new QueryMenu(name, false); 3062 queryMenu->SetTargetForItems(be_app); 3063 queryMenu->SetPredicate(queryString); 3064 fQueryMenu->AddItem(queryMenu); 3065 3066 free(queryString); 3067 } 3068 3069 if (queryItemsAdded) 3070 fQueryMenu->AddItem(new BSeparatorItem(), 1); 3071 } 3072 3073 3074 char* 3075 TMailWindow::_BuildQueryString(BEntry* entry) const 3076 { 3077 BNode node(entry); 3078 if (node.InitCheck() != B_OK) 3079 return NULL; 3080 3081 uint32 mode; 3082 if (node.ReadAttr(kAttrQueryInitialMode, B_INT32_TYPE, 0, (int32*)&mode, 3083 sizeof(int32)) <= 0) { 3084 mode = kByNameItem; 3085 } 3086 3087 BString queryString; 3088 switch (mode) { 3089 case kByForumlaItem: 3090 { 3091 BString buffer; 3092 if (node.ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK) 3093 queryString << buffer; 3094 break; 3095 } 3096 3097 case kByNameItem: 3098 { 3099 BString buffer; 3100 if (node.ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK) 3101 queryString << "(name==*" << buffer << "*)"; 3102 break; 3103 } 3104 3105 case kByAttributeItem: 3106 { 3107 int32 count = 1; 3108 if (node.ReadAttr(kAttrQueryInitialNumAttrs, B_INT32_TYPE, 0, 3109 (int32*)&count, sizeof(int32)) <= 0) { 3110 count = 1; 3111 } 3112 3113 attr_info info; 3114 if (node.GetAttrInfo(kAttrQueryInitialAttrs, &info) != B_OK) 3115 break; 3116 3117 if (count > 1) 3118 queryString << "("; 3119 3120 char* buffer = new char[info.size]; 3121 if (node.ReadAttr(kAttrQueryInitialAttrs, B_MESSAGE_TYPE, 0, 3122 buffer, (size_t)info.size) == info.size) { 3123 BMessage message; 3124 if (message.Unflatten(buffer) == B_OK) { 3125 for (int32 index = 0; /*index < count*/; index++) { 3126 const char* field; 3127 const char* value; 3128 if (message.FindString("menuSelection", index, &field) 3129 != B_OK 3130 || message.FindString("attrViewText", index, &value) 3131 != B_OK) { 3132 break; 3133 } 3134 3135 // ignore the mime type, we'll force it to be email 3136 // later 3137 if (strcmp(field, "BEOS:TYPE") != 0) { 3138 // TODO: check if subMenu contains the type of 3139 // comparison we are suppose to make here 3140 queryString << "(" << field << "==\"" 3141 << value << "\")"; 3142 3143 int32 logicMenuSelectedIndex; 3144 if (message.FindInt32("logicalRelation", index, 3145 &logicMenuSelectedIndex) == B_OK) { 3146 if (logicMenuSelectedIndex == 0) 3147 queryString << "&&"; 3148 else if (logicMenuSelectedIndex == 1) 3149 queryString << "||"; 3150 } else 3151 break; 3152 } 3153 } 3154 } 3155 } 3156 3157 if (count > 1) 3158 queryString << ")"; 3159 3160 delete [] buffer; 3161 break; 3162 } 3163 3164 default: 3165 break; 3166 } 3167 3168 if (queryString.Length() == 0) 3169 return NULL; 3170 3171 // force it to check for email only 3172 if (queryString.FindFirst("text/x-email") < 0) { 3173 BString temp; 3174 temp << "(" << queryString << "&&(BEOS:TYPE==\"text/x-email\"))"; 3175 queryString = temp; 3176 } 3177 3178 return strdup(queryString.String()); 3179 } 3180 3181 3182 void 3183 TMailWindow::_AddReadButton() 3184 { 3185 BNode node(fRef); 3186 3187 read_flags flag = B_UNREAD; 3188 read_read_attr(node, flag); 3189 3190 if (flag == B_READ) { 3191 fToolBar->SetActionVisible(M_UNREAD, true); 3192 fToolBar->SetActionVisible(M_READ, false); 3193 } else { 3194 fToolBar->SetActionVisible(M_UNREAD, false); 3195 fToolBar->SetActionVisible(M_READ, true); 3196 } 3197 } 3198 3199 3200 void 3201 TMailWindow::_UpdateReadButton() 3202 { 3203 if (fApp->ShowToolBar()) { 3204 if (!fAutoMarkRead && fIncoming) 3205 _AddReadButton(); 3206 } 3207 UpdateViews(); 3208 } 3209 3210 3211 void 3212 TMailWindow::_UpdateLabel(uint32 command, const char* label, bool show) 3213 { 3214 BButton* button = fToolBar->FindButton(command); 3215 if (button != NULL) { 3216 button->SetLabel(show ? label : NULL); 3217 button->SetToolTip(show ? NULL : label); 3218 } 3219 } 3220 3221 3222 void 3223 TMailWindow::_SetDownloading(bool downloading) 3224 { 3225 fDownloading = downloading; 3226 } 3227 3228 3229 uint32 3230 TMailWindow::_CurrentCharacterSet() const 3231 { 3232 uint32 defaultCharSet = fResending || !fIncoming 3233 ? fApp->MailCharacterSet() : B_MAIL_NULL_CONVERSION; 3234 3235 BMenuItem* marked = fEncodingMenu->FindMarked(); 3236 if (marked == NULL) 3237 return defaultCharSet; 3238 3239 return marked->Message()->GetInt32("charset", defaultCharSet); 3240 } 3241