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