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