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