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