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