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