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