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