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 #ifdef __HAIKU__ 52 # include <PathMonitor.h> 53 #endif 54 #include <Roster.h> 55 #include <Screen.h> 56 #include <StorageKit.h> 57 #include <String.h> 58 #include <TextView.h> 59 #include <UTF8.h> 60 61 #include <fs_index.h> 62 #include <fs_info.h> 63 64 #include <MailMessage.h> 65 #include <MailSettings.h> 66 #include <MailDaemon.h> 67 #include <mail_util.h> 68 #include <MDRLanguage.h> 69 70 #include <CharacterSetRoster.h> 71 72 using namespace BPrivate ; 73 74 #ifdef HAIKU_TARGET_PLATFORM_BEOS 75 #include <netdb.h> 76 #endif 77 78 #include "ButtonBar.h" 79 #include "Content.h" 80 #include "Enclosures.h" 81 #include "FieldMsg.h" 82 #include "FindWindow.h" 83 #include "Header.h" 84 #include "Messages.h" 85 #include "MailApp.h" 86 #include "MailPopUpMenu.h" 87 #include "MailSupport.h" 88 #include "Prefs.h" 89 #include "QueryMenu.h" 90 #include "Signature.h" 91 #include "Status.h" 92 #include "String.h" 93 #include "Utilities.h" 94 #include "Words.h" 95 96 97 const char *kUndoStrings[] = { 98 MDR_DIALECT_CHOICE ("Undo","Z) 取り消し"), 99 MDR_DIALECT_CHOICE ("Undo Typing","Z) 取り消し(入力)"), 100 MDR_DIALECT_CHOICE ("Undo Cut","Z) 取り消し(切り取り)"), 101 MDR_DIALECT_CHOICE ("Undo Paste","Z) 取り消し(貼り付け)"), 102 MDR_DIALECT_CHOICE ("Undo Clear","Z) 取り消し(消去)"), 103 MDR_DIALECT_CHOICE ("Undo Drop","Z) 取り消し(ドロップ)") 104 }; 105 106 const char *kRedoStrings[] = { 107 MDR_DIALECT_CHOICE ("Redo", "Z) やり直し"), 108 MDR_DIALECT_CHOICE ("Redo Typing", "Z) やり直し(入力)"), 109 MDR_DIALECT_CHOICE ("Redo Cut", "Z) やり直し(切り取り)"), 110 MDR_DIALECT_CHOICE ("Redo Paste", "Z) やり直し(貼り付け)"), 111 MDR_DIALECT_CHOICE ("Redo Clear", "Z) やり直し(消去)"), 112 MDR_DIALECT_CHOICE ("Redo Drop", "Z) やり直し(ドロップ)") 113 }; 114 115 116 // Text for both the main menu and the pop-up menu. 117 static const char *kSpamMenuItemTextArray[] = { 118 "Mark as Spam and Move to Trash", // M_TRAIN_SPAM_AND_DELETE 119 "Mark as Spam", // M_TRAIN_SPAM 120 "Unmark this Message", // M_UNTRAIN 121 "Mark as Genuine" // M_TRAIN_GENUINE 122 }; 123 124 static const uint32 kMsgQuitAndKeepAllStatus = 'Casm'; 125 126 static const char *kQueriesDirectory = "mail/queries"; 127 static const char *kAttrQueryInitialMode = "_trk/qryinitmode"; // 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'; // taken from src/kits/tracker/FindPanel.h 132 static const uint32 kByNameItem = 'Fbyn'; // taken from src/kits/tracker/FindPanel.h 133 static const uint32 kByAttributeItem = 'Fbya'; // taken from src/kits/tracker/FindPanel.h 134 static const uint32 kByForumlaItem = 'Fbyq'; // taken from src/kits/tracker/FindPanel.h 135 136 137 // static list for tracking of Windows 138 BList TMailWindow::sWindowList; 139 BLocker TMailWindow::sWindowListLock; 140 141 static bool sKeepStatusOnQuit; 142 143 144 // #pragma mark - 145 146 147 TMailWindow::TMailWindow(BRect rect, const char* title, TMailApp* app, 148 const entry_ref* ref, const char* to, const BFont* font, bool resending, 149 BMessenger* messenger) 150 : BWindow(rect, title, B_DOCUMENT_WINDOW, 0), 151 fApp(app), 152 fFieldState(0), 153 fPanel(NULL), 154 fSendButton(NULL), 155 fSaveButton(NULL), 156 fPrintButton(NULL), 157 fSigButton(NULL), 158 fZoom(rect), 159 fEnclosuresView(NULL), 160 fPrevTrackerPositionSaved(false), 161 fNextTrackerPositionSaved(false), 162 fSigAdded(false), 163 fReplying(false), 164 fResending(resending), 165 fSent(false), 166 fDraft(false), 167 fChanged(false), 168 fStartingText(NULL), 169 fOriginatingWindow(NULL) 170 { 171 if (messenger != NULL) 172 fTrackerMessenger = *messenger; 173 174 char str[256]; 175 char status[272]; 176 uint32 message; 177 float height; 178 BMenu* menu; 179 BMenu* subMenu; 180 BMenuItem* item; 181 BMessage* msg; 182 attr_info info; 183 BFile file(ref, B_READ_ONLY); 184 185 if (ref) { 186 fRef = new entry_ref(*ref); 187 fMail = new BEmailMessage(fRef); 188 fIncoming = true; 189 } else { 190 fRef = NULL; 191 fMail = NULL; 192 fIncoming = false; 193 } 194 195 BRect r(0, 0, RIGHT_BOUNDARY, 15); 196 fMenuBar = new BMenuBar(r, ""); 197 198 // File Menu 199 200 menu = new BMenu(MDR_DIALECT_CHOICE ("File","F) ファイル")); 201 202 msg = new BMessage(M_NEW); 203 msg->AddInt32("type", M_NEW); 204 menu->AddItem(item = new BMenuItem(MDR_DIALECT_CHOICE ( 205 "New Mail Message", "N) 新規メッセージ作成"), msg, 'N')); 206 item->SetTarget(be_app); 207 208 // Cheap hack - only show the drafts menu when composing messages. Insert 209 // a "true || " in the following IF statement if you want the old BeMail 210 // behaviour. The difference is that without live draft menu updating you 211 // can open around 100 e-mails (the BeOS maximum number of open files) 212 // rather than merely around 20, since each open draft-monitoring query 213 // sucks up one file handle per mounted BFS disk volume. Plus mail file 214 // opening speed is noticably improved! ToDo: change this to populate the 215 // Draft menu with the file names on demand - when the user clicks on it; 216 // don't need a live query since the menu isn't staying up for more than a 217 // few seconds. 218 219 if (!fIncoming) { 220 QueryMenu *queryMenu; 221 queryMenu = new QueryMenu(MDR_DIALECT_CHOICE ("Open Draft", "O) ドラフトを開く"), false); 222 queryMenu->SetTargetForItems(be_app); 223 224 queryMenu->SetPredicate("MAIL:draft==1"); 225 menu->AddItem(queryMenu); 226 } 227 228 if (!fIncoming || resending) { 229 menu->AddItem(fSendLater = new BMenuItem( 230 MDR_DIALECT_CHOICE ("Save as Draft", "S)ドラフトとして保存"), 231 new BMessage(M_SAVE_AS_DRAFT), 'S')); 232 } 233 234 if (!resending && fIncoming) { 235 menu->AddSeparatorItem(); 236 237 subMenu = new BMenu(MDR_DIALECT_CHOICE ("Close and ","C) 閉じる")); 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( 245 MDR_DIALECT_CHOICE ("Leave as New", "N) 新規<New>のままにする"), 246 new BMessage(M_CLOSE_SAME), 'W', B_SHIFT_KEY)); 247 #if 0 248 subMenu->AddItem(item = new BMenuItem( 249 MDR_DIALECT_CHOICE ("Set to Read", "R) 開封済<Read>に設定"), 250 new BMessage(M_CLOSE_READ), 'W')); 251 #endif 252 message = M_CLOSE_READ; 253 } else { 254 if (strlen(str)) 255 sprintf(status, MDR_DIALECT_CHOICE ("Leave as '%s'","W) 属性を<%s>にする"), str); 256 else 257 sprintf(status, MDR_DIALECT_CHOICE ("Leave same","W) 属性はそのまま")); 258 subMenu->AddItem(item = new BMenuItem(status, 259 new BMessage(M_CLOSE_SAME), 'W')); 260 message = M_CLOSE_SAME; 261 AddShortcut('W', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(M_CLOSE_SAME)); 262 } 263 264 subMenu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE("Move to Trash", 265 "T) 削除"), new BMessage(M_DELETE), 'T', B_CONTROL_KEY)); 266 AddShortcut('T', B_SHIFT_KEY | B_COMMAND_KEY, new BMessage(M_DELETE_NEXT)); 267 268 subMenu->AddSeparatorItem(); 269 270 subMenu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE("Set to Saved", 271 "S) 属性を<Saved>に設定"), new BMessage(M_CLOSE_SAVED), 'W', B_CONTROL_KEY)); 272 273 if (add_query_menu_items(subMenu, INDEX_STATUS, M_STATUS, 274 MDR_DIALECT_CHOICE("Set to %s", "属性を<%s>に設定")) > 0) 275 subMenu->AddSeparatorItem(); 276 277 subMenu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE("Set to", "X) 他の属性に変更") 278 B_UTF8_ELLIPSIS, new BMessage(M_CLOSE_CUSTOM))); 279 280 #if 0 281 subMenu->AddItem(new BMenuItem(new TMenu( 282 MDR_DIALECT_CHOICE ("Set to", "X) 他の属性に変更")B_UTF8_ELLIPSIS, 283 INDEX_STATUS, M_STATUS, false, false), new BMessage(M_CLOSE_CUSTOM))); 284 #endif 285 menu->AddItem(subMenu); 286 } else { 287 menu->AddSeparatorItem(); 288 menu->AddItem(new BMenuItem( 289 MDR_DIALECT_CHOICE ("Close", "W) 閉じる"), 290 new BMessage(B_CLOSE_REQUESTED), 'W')); 291 } 292 293 menu->AddSeparatorItem(); 294 menu->AddItem(fPrint = new BMenuItem( 295 MDR_DIALECT_CHOICE ("Page Setup", "G) ページ設定") B_UTF8_ELLIPSIS, 296 new BMessage(M_PRINT_SETUP))); 297 menu->AddItem(fPrint = new BMenuItem( 298 MDR_DIALECT_CHOICE ("Print", "P) 印刷") B_UTF8_ELLIPSIS, 299 new BMessage(M_PRINT), 'P')); 300 fMenuBar->AddItem(menu); 301 302 menu->AddSeparatorItem(); 303 menu->AddItem(item = new BMenuItem( 304 MDR_DIALECT_CHOICE ("About Mail", "A) Mailについて") B_UTF8_ELLIPSIS, 305 new BMessage(B_ABOUT_REQUESTED))); 306 item->SetTarget(be_app); 307 308 menu->AddSeparatorItem(); 309 menu->AddItem(item = new BMenuItem( 310 MDR_DIALECT_CHOICE ("Quit", "Q) 終了"), 311 new BMessage(B_QUIT_REQUESTED), 'Q')); 312 item->SetTarget(be_app); 313 314 // Edit Menu 315 316 menu = new BMenu(MDR_DIALECT_CHOICE ("Edit","E) 編集")); 317 menu->AddItem(fUndo = new BMenuItem(MDR_DIALECT_CHOICE ("Undo","Z) 元に戻す"), new BMessage(B_UNDO), 'Z', 0)); 318 fUndo->SetTarget(NULL, this); 319 menu->AddItem(fRedo = new BMenuItem(MDR_DIALECT_CHOICE ("Redo","Z) やり直し"), new BMessage(M_REDO), 'Z', B_SHIFT_KEY)); 320 fRedo->SetTarget(NULL, this); 321 menu->AddSeparatorItem(); 322 menu->AddItem(fCut = new BMenuItem(MDR_DIALECT_CHOICE ("Cut","X) 切り取り"), new BMessage(B_CUT), 'X')); 323 fCut->SetTarget(NULL, this); 324 menu->AddItem(fCopy = new BMenuItem(MDR_DIALECT_CHOICE ("Copy","C) コピー"), new BMessage(B_COPY), 'C')); 325 fCopy->SetTarget(NULL, this); 326 menu->AddItem(fPaste = new BMenuItem(MDR_DIALECT_CHOICE ("Paste","V) 貼り付け"), new BMessage(B_PASTE), 'V')); 327 fPaste->SetTarget(NULL, this); 328 menu->AddSeparatorItem(); 329 menu->AddItem(item = new BMenuItem(MDR_DIALECT_CHOICE ("Select All", "A) 全文選択"), new BMessage(M_SELECT), 'A')); 330 menu->AddSeparatorItem(); 331 item->SetTarget(NULL, this); 332 menu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Find", "F) 検索") B_UTF8_ELLIPSIS, new BMessage(M_FIND), 'F')); 333 menu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Find Again", "G) 次を検索"), new BMessage(M_FIND_AGAIN), 'G')); 334 if (!fIncoming) { 335 menu->AddSeparatorItem(); 336 menu->AddItem(fQuote =new BMenuItem( 337 MDR_DIALECT_CHOICE ("Quote","Q) 引用符をつける"), 338 new BMessage(M_QUOTE), B_RIGHT_ARROW)); 339 menu->AddItem(fRemoveQuote = new BMenuItem( 340 MDR_DIALECT_CHOICE ("Remove Quote","R) 引用符を削除"), 341 new BMessage(M_REMOVE_QUOTE), B_LEFT_ARROW)); 342 menu->AddSeparatorItem(); 343 fSpelling = new BMenuItem( 344 MDR_DIALECT_CHOICE ("Check Spelling","H) スペルチェック"), 345 new BMessage( M_CHECK_SPELLING ), ';' ); 346 menu->AddItem(fSpelling); 347 if (fApp->StartWithSpellCheckOn()) 348 PostMessage (M_CHECK_SPELLING); 349 } 350 menu->AddSeparatorItem(); 351 menu->AddItem(item = new BMenuItem( 352 MDR_DIALECT_CHOICE ("Preferences","P) Mailの設定") B_UTF8_ELLIPSIS, 353 new BMessage(M_PREFS),',')); 354 item->SetTarget(be_app); 355 fMenuBar->AddItem(menu); 356 menu->AddItem(item = new BMenuItem( 357 MDR_DIALECT_CHOICE ("Accounts","Accounts") B_UTF8_ELLIPSIS, 358 new BMessage(M_ACCOUNTS),'-')); 359 item->SetTarget(be_app); 360 361 // View Menu 362 363 if (!resending && fIncoming) { 364 menu = new BMenu("View"); 365 menu->AddItem(fHeader = new BMenuItem(MDR_DIALECT_CHOICE ("Show Header","H) ヘッダーを表示"), new BMessage(M_HEADER), 'H')); 366 menu->AddItem(fRaw = new BMenuItem(MDR_DIALECT_CHOICE ("Show Raw Message"," メッセージを生で表示"), new BMessage(M_RAW))); 367 fMenuBar->AddItem(menu); 368 } 369 370 // Message Menu 371 372 menu = new BMenu(MDR_DIALECT_CHOICE ("Message", "M) メッセージ")); 373 374 if (!resending && fIncoming) { 375 BMenuItem *menuItem; 376 menu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Reply","R) 返信"), new BMessage(M_REPLY),'R')); 377 menu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Reply to Sender","S) 送信者に返信"), new BMessage(M_REPLY_TO_SENDER),'R',B_OPTION_KEY)); 378 menu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Reply to All","P) 全員に返信"), new BMessage(M_REPLY_ALL), 'R', B_SHIFT_KEY)); 379 380 menu->AddSeparatorItem(); 381 382 menu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Forward","J) 転送"), new BMessage(M_FORWARD), 'J')); 383 menu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Forward without Attachments","The opposite: F) 添付ファイルを含めて転送"), new BMessage(M_FORWARD_WITHOUT_ATTACHMENTS))); 384 menu->AddItem(menuItem = new BMenuItem(MDR_DIALECT_CHOICE ("Resend"," 再送信"), new BMessage(M_RESEND))); 385 menu->AddItem(menuItem = new BMenuItem(MDR_DIALECT_CHOICE ("Copy to New","D) 新規メッセージへコピー"), new BMessage(M_COPY_TO_NEW), 'D')); 386 387 menu->AddSeparatorItem(); 388 fDeleteNext = new BMenuItem(MDR_DIALECT_CHOICE ("Move to Trash","T) 削除"), new BMessage(M_DELETE_NEXT), 'T'); 389 menu->AddItem(fDeleteNext); 390 menu->AddSeparatorItem(); 391 392 fPrevMsg = new BMenuItem(MDR_DIALECT_CHOICE ("Previous Message","B) 前のメッセージ"), new BMessage(M_PREVMSG), 393 B_UP_ARROW); 394 menu->AddItem(fPrevMsg); 395 fNextMsg = new BMenuItem(MDR_DIALECT_CHOICE ("Next Message","N) 次のメッセージ"), new BMessage(M_NEXTMSG), 396 B_DOWN_ARROW); 397 menu->AddItem(fNextMsg); 398 menu->AddSeparatorItem(); 399 fSaveAddrMenu = subMenu = new BMenu(MDR_DIALECT_CHOICE ("Save Address", " アドレスを保存")); 400 401 // create the list of addresses 402 403 BList addressList; 404 get_address_list(addressList, fMail->To(), extract_address); 405 get_address_list(addressList, fMail->CC(), extract_address); 406 get_address_list(addressList, fMail->From(), extract_address); 407 get_address_list(addressList, fMail->ReplyTo(), extract_address); 408 409 for (int32 i = addressList.CountItems(); i-- > 0;) { 410 char *address = (char *)addressList.RemoveItem(0L); 411 412 // insert the new address in alphabetical order 413 int32 index = 0; 414 while ((item = subMenu->ItemAt(index)) != NULL) { 415 if (!strcmp(address, item->Label())) { 416 // item already in list 417 goto skip; 418 } 419 420 if (strcmp(address, item->Label()) < 0) 421 break; 422 423 index++; 424 } 425 426 msg = new BMessage(M_SAVE); 427 msg->AddString("address", address); 428 subMenu->AddItem(new BMenuItem(address, msg), index); 429 430 skip: 431 free(address); 432 } 433 434 menu->AddItem(subMenu); 435 fMenuBar->AddItem(menu); 436 437 // Spam Menu 438 439 if (fApp->ShowSpamGUI()) { 440 menu = new BMenu("Spam Filtering"); 441 menu->AddItem(new BMenuItem("Mark as Spam and Move to Trash", 442 new BMessage(M_TRAIN_SPAM_AND_DELETE), 'K')); 443 menu->AddItem(new BMenuItem("Mark as Spam", 444 new BMessage(M_TRAIN_SPAM), 'K', B_OPTION_KEY)); 445 menu->AddSeparatorItem(); 446 menu->AddItem(new BMenuItem("Unmark this Message", 447 new BMessage(M_UNTRAIN))); 448 menu->AddSeparatorItem(); 449 menu->AddItem(new BMenuItem("Mark as Genuine", 450 new BMessage(M_TRAIN_GENUINE), 'K', B_SHIFT_KEY)); 451 fMenuBar->AddItem(menu); 452 } 453 } else { 454 menu->AddItem(fSendNow = new BMenuItem( 455 MDR_DIALECT_CHOICE ("Send Message", "M) メッセージを送信"), 456 new BMessage(M_SEND_NOW), 'M')); 457 458 if (!fIncoming) { 459 menu->AddSeparatorItem(); 460 fSignature = new TMenu( 461 MDR_DIALECT_CHOICE ("Add Signature", "D) 署名を追加"), 462 INDEX_SIGNATURE, M_SIGNATURE); 463 menu->AddItem(new BMenuItem(fSignature)); 464 menu->AddItem(item = new BMenuItem( 465 MDR_DIALECT_CHOICE ("Edit Signatures","S) 署名の編集") B_UTF8_ELLIPSIS, 466 new BMessage(M_EDIT_SIGNATURE))); 467 item->SetTarget(be_app); 468 menu->AddSeparatorItem(); 469 menu->AddItem(fAdd = new BMenuItem(MDR_DIALECT_CHOICE ("Add Enclosure","E) 追加")B_UTF8_ELLIPSIS, new BMessage(M_ADD), 'E')); 470 menu->AddItem(fRemove = new BMenuItem(MDR_DIALECT_CHOICE ("Remove Enclosure","T) 削除"), new BMessage(M_REMOVE), 'T')); 471 } 472 fMenuBar->AddItem(menu); 473 } 474 475 // Queries Menu 476 477 fQueryMenu = new BMenu(MDR_DIALECT_CHOICE("Queries","???")); 478 fMenuBar->AddItem(fQueryMenu); 479 480 _RebuildQueryMenu(true); 481 482 // Menu Bar 483 484 AddChild(fMenuBar); 485 height = fMenuBar->Bounds().bottom + 1; 486 487 // Button Bar 488 489 float bbwidth = 0, bbheight = 0; 490 491 bool showButtonBar = fApp->ShowButtonBar(); 492 493 if (showButtonBar) { 494 BuildButtonBar(); 495 fButtonBar->ShowLabels(showButtonBar); 496 fButtonBar->Arrange(MDR_DIALECT_CHOICE(true, true)); 497 fButtonBar->GetPreferredSize(&bbwidth, &bbheight); 498 fButtonBar->ResizeTo(Bounds().right, bbheight); 499 fButtonBar->MoveTo(0, height); 500 fButtonBar->Show(); 501 } else 502 fButtonBar = NULL; 503 504 r.top = r.bottom = height + bbheight + 1; 505 fHeaderView = new THeaderView (r, rect, fIncoming, fMail, resending, 506 (resending || !fIncoming) 507 ? fApp->MailCharacterSet() // Use preferences setting for composing mail. 508 : B_MAIL_NULL_CONVERSION, // Default is automatic selection for reading mail. 509 fApp->DefaultChain()); 510 511 r = Frame(); 512 r.OffsetTo(0, 0); 513 r.top = fHeaderView->Frame().bottom - 1; 514 fContentView = new TContentView(r, fIncoming, fMail, 515 const_cast<BFont *>(font), false, fApp->ColoredQuotes()); 516 // TContentView needs to be properly const, for now cast away constness 517 518 AddChild(fHeaderView); 519 if (fEnclosuresView) 520 AddChild(fEnclosuresView); 521 AddChild(fContentView); 522 523 if (to) 524 fHeaderView->fTo->SetText(to); 525 526 AddShortcut('n', B_COMMAND_KEY, new BMessage(M_NEW)); 527 528 // If auto-signature, add signature to the text here. 529 530 BString signature = fApp->Signature(); 531 532 if (!fIncoming && strcmp(signature.String(), SIG_NONE) != 0) { 533 if (strcmp(signature.String(), SIG_RANDOM) == 0) 534 PostMessage(M_RANDOM_SIG); 535 else { 536 // Create a query to find this signature 537 BVolume volume; 538 BVolumeRoster().GetBootVolume(&volume); 539 540 BQuery query; 541 query.SetVolume(&volume); 542 query.PushAttr(INDEX_SIGNATURE); 543 query.PushString(signature.String()); 544 query.PushOp(B_EQ); 545 query.Fetch(); 546 547 // If we find the named query, add it to the text. 548 BEntry entry; 549 if (query.GetNextEntry(&entry) == B_NO_ERROR) { 550 off_t size; 551 BFile file; 552 file.SetTo(&entry, O_RDWR); 553 if (file.InitCheck() == B_NO_ERROR) { 554 file.GetSize(&size); 555 char *str = (char *)malloc(size); 556 size = file.Read(str, size); 557 558 fContentView->fTextView->Insert(str, size); 559 fContentView->fTextView->GoToLine(0); 560 fContentView->fTextView->ScrollToSelection(); 561 562 fStartingText = (char *)malloc(size = strlen(fContentView->fTextView->Text()) + 1); 563 if (fStartingText != NULL) 564 strcpy(fStartingText, fContentView->fTextView->Text()); 565 } 566 } else { 567 char tempString [2048]; 568 query.GetPredicate (tempString, sizeof (tempString)); 569 printf ("Query failed, was looking for: %s\n", tempString); 570 } 571 } 572 } 573 574 if (fRef) 575 SetTitleForMessage(); 576 577 _UpdateSizeLimits(); 578 579 AddShortcut('q', B_SHIFT_KEY, new BMessage(kMsgQuitAndKeepAllStatus)); 580 } 581 582 583 void 584 TMailWindow::BuildButtonBar() 585 { 586 ButtonBar *bbar; 587 588 bbar = new ButtonBar(BRect(0, 0, 100, 100), "ButtonBar", 2, 3, 0, 1, 10, 2); 589 bbar->AddButton(MDR_DIALECT_CHOICE ("New","新規"), 28, new BMessage(M_NEW)); 590 bbar->AddDivider(5); 591 592 if (fResending) { 593 fSendButton = bbar->AddButton(MDR_DIALECT_CHOICE ("Send","送信"), 8, new BMessage(M_SEND_NOW)); 594 bbar->AddDivider(5); 595 } else if (!fIncoming) { 596 fSendButton = bbar->AddButton(MDR_DIALECT_CHOICE ("Send","送信"), 8, new BMessage(M_SEND_NOW)); 597 fSendButton->SetEnabled(false); 598 fSigButton = bbar->AddButton(MDR_DIALECT_CHOICE ("Signature","署名"), 4, new BMessage(M_SIG_MENU)); 599 fSigButton->InvokeOnButton(B_SECONDARY_MOUSE_BUTTON); 600 fSaveButton = bbar->AddButton(MDR_DIALECT_CHOICE ("Save","保存"), 44, new BMessage(M_SAVE_AS_DRAFT)); 601 fSaveButton->SetEnabled(false); 602 fPrintButton = bbar->AddButton(MDR_DIALECT_CHOICE ("Print","印刷"), 16, new BMessage(M_PRINT)); 603 fPrintButton->SetEnabled(false); 604 bbar->AddButton(MDR_DIALECT_CHOICE ("Trash","削除"), 0, new BMessage(M_DELETE)); 605 bbar->AddDivider(5); 606 } else { 607 BmapButton *button = bbar->AddButton(MDR_DIALECT_CHOICE ("Reply","返信"), 12, new BMessage(M_REPLY)); 608 button->InvokeOnButton(B_SECONDARY_MOUSE_BUTTON); 609 button = bbar->AddButton(MDR_DIALECT_CHOICE ("Forward","転送"), 40, new BMessage(M_FORWARD)); 610 button->InvokeOnButton(B_SECONDARY_MOUSE_BUTTON); 611 fPrintButton = bbar->AddButton(MDR_DIALECT_CHOICE ("Print","印刷"), 16, new BMessage(M_PRINT)); 612 bbar->AddButton(MDR_DIALECT_CHOICE ("Trash","削除"), 0, new BMessage(M_DELETE_NEXT)); 613 if (fApp->ShowSpamGUI()) { 614 button = bbar->AddButton("Spam", 48, new BMessage(M_SPAM_BUTTON)); 615 button->InvokeOnButton(B_SECONDARY_MOUSE_BUTTON); 616 } 617 bbar->AddDivider(5); 618 bbar->AddButton(MDR_DIALECT_CHOICE ("Next","次へ"), 24, new BMessage(M_NEXTMSG)); 619 bbar->AddButton(MDR_DIALECT_CHOICE ("Previous","前へ"), 20, new BMessage(M_PREVMSG)); 620 } 621 bbar->AddButton(MDR_DIALECT_CHOICE ("Inbox","受信箱"), 36, new BMessage(M_OPEN_MAIL_BOX)); 622 bbar->AddButton(MDR_DIALECT_CHOICE ("Mail","メール"), 32, new BMessage(M_OPEN_MAIL_FOLDER)); 623 624 bbar->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 625 bbar->Hide(); 626 AddChild(bbar); 627 fButtonBar = bbar; 628 } 629 630 631 void 632 TMailWindow::UpdateViews() 633 { 634 float bbwidth = 0, bbheight = 0; 635 float nextY = fMenuBar->Frame().bottom+1; 636 637 bool showButtonBar = fApp->ShowButtonBar(); 638 639 // Show/Hide Button Bar 640 if (showButtonBar) { 641 // Create the Button Bar if needed 642 if (!fButtonBar) 643 BuildButtonBar(); 644 fButtonBar->ShowLabels(showButtonBar); 645 fButtonBar->Arrange(/* True for all buttons same size, false to just fit */ 646 MDR_DIALECT_CHOICE (true, true)); 647 fButtonBar->GetPreferredSize( &bbwidth, &bbheight); 648 fButtonBar->ResizeTo(Bounds().right, bbheight); 649 fButtonBar->MoveTo(0, nextY); 650 nextY += bbheight + 1; 651 if (fButtonBar->IsHidden()) 652 fButtonBar->Show(); 653 else 654 fButtonBar->Invalidate(); 655 } else if (fButtonBar) 656 fButtonBar->Hide(); 657 658 // Arange other views to match 659 fHeaderView->MoveTo(0, nextY); 660 nextY = fHeaderView->Frame().bottom; 661 if (fEnclosuresView) { 662 fEnclosuresView->MoveTo(0, nextY); 663 nextY = fEnclosuresView->Frame().bottom+1; 664 } 665 BRect bounds(Bounds()); 666 fContentView->MoveTo(0, nextY-1); 667 fContentView->ResizeTo(bounds.right-bounds.left, bounds.bottom-nextY+1); 668 669 _UpdateSizeLimits(); 670 } 671 672 673 TMailWindow::~TMailWindow() 674 { 675 fApp->SetLastWindowFrame(Frame()); 676 677 delete fMail; 678 delete fPanel; 679 delete fOriginatingWindow; 680 681 BAutolock locker(sWindowListLock); 682 sWindowList.RemoveItem(this); 683 } 684 685 686 status_t 687 TMailWindow::GetMailNodeRef(node_ref &nodeRef) const 688 { 689 if (fRef == NULL) 690 return B_ERROR; 691 692 BNode node(fRef); 693 return node.GetNodeRef(&nodeRef); 694 } 695 696 697 bool 698 TMailWindow::GetTrackerWindowFile(entry_ref *ref, bool next) const 699 { 700 // Position was already saved 701 if (next && fNextTrackerPositionSaved) { 702 *ref = fNextRef; 703 return true; 704 } 705 if (!next && fPrevTrackerPositionSaved) { 706 *ref = fPrevRef; 707 return true; 708 } 709 710 if (!fTrackerMessenger.IsValid()) 711 return false; 712 713 // Ask the tracker what the next/prev file in the window is. 714 // Continue asking for the next reference until a valid 715 // email file is found (ignoring other types). 716 entry_ref nextRef = *ref; 717 bool foundRef = false; 718 while (!foundRef) { 719 BMessage request(B_GET_PROPERTY); 720 BMessage spc; 721 if (next) 722 spc.what = 'snxt'; 723 else 724 spc.what = 'sprv'; 725 726 spc.AddString("property", "Entry"); 727 spc.AddRef("data", &nextRef); 728 729 request.AddSpecifier(&spc); 730 BMessage reply; 731 if (fTrackerMessenger.SendMessage(&request, &reply) != B_OK) 732 return false; 733 734 if (reply.FindRef("result", &nextRef) != B_OK) 735 return false; 736 737 char fileType[256]; 738 BNode node(&nextRef); 739 if (node.InitCheck() != B_OK) 740 return false; 741 742 if (BNodeInfo(&node).GetType(fileType) != B_OK) 743 return false; 744 745 if (strcasecmp(fileType,"text/x-email") == 0) 746 foundRef = true; 747 } 748 749 *ref = nextRef; 750 return foundRef; 751 } 752 753 754 void 755 TMailWindow::SaveTrackerPosition(entry_ref *ref) 756 { 757 // if only one of them is saved, we're not going to do it again 758 if (fNextTrackerPositionSaved || fPrevTrackerPositionSaved) 759 return; 760 761 fNextRef = fPrevRef = *ref; 762 763 fNextTrackerPositionSaved = GetTrackerWindowFile(&fNextRef, true); 764 fPrevTrackerPositionSaved = GetTrackerWindowFile(&fPrevRef, false); 765 } 766 767 768 void 769 TMailWindow::SetOriginatingWindow(BWindow *window) 770 { 771 delete fOriginatingWindow; 772 fOriginatingWindow = new BMessenger(window); 773 } 774 775 776 void 777 TMailWindow::SetTrackerSelectionToCurrent() 778 { 779 BMessage setSelection(B_SET_PROPERTY); 780 setSelection.AddSpecifier("Selection"); 781 setSelection.AddRef("data", fRef); 782 783 fTrackerMessenger.SendMessage(&setSelection); 784 } 785 786 787 void 788 TMailWindow::SetCurrentMessageRead() 789 { 790 BNode node(fRef); 791 if (node.InitCheck() == B_NO_ERROR) { 792 BString status; 793 if (ReadAttrString(&node, B_MAIL_ATTR_STATUS, &status) == B_NO_ERROR 794 && !status.ICompare("New")) { 795 node.RemoveAttr(B_MAIL_ATTR_STATUS); 796 WriteAttrString(&node, B_MAIL_ATTR_STATUS, "Read"); 797 } 798 } 799 } 800 801 802 void 803 TMailWindow::FrameResized(float width, float height) 804 { 805 fContentView->FrameResized(width, height); 806 } 807 808 809 void 810 TMailWindow::MenusBeginning() 811 { 812 bool enable; 813 int32 finish = 0; 814 int32 start = 0; 815 BTextView *textView; 816 817 if (!fIncoming) { 818 bool gotToField = fHeaderView->fTo->Text()[0] != 0; 819 bool gotCcField = fHeaderView->fCc->Text()[0] != 0; 820 bool gotBccField = fHeaderView->fBcc->Text()[0] != 0; 821 bool gotSubjectField = fHeaderView->fSubject->Text()[0] != 0; 822 bool gotText = fContentView->fTextView->Text()[0] != 0; 823 fSendNow->SetEnabled(gotToField || gotBccField); 824 fSendLater->SetEnabled(fChanged && (gotToField || gotCcField 825 || gotBccField || gotSubjectField || gotText)); 826 827 be_clipboard->Lock(); 828 fPaste->SetEnabled(be_clipboard->Data()->HasData("text/plain", B_MIME_TYPE) 829 && (fEnclosuresView == NULL || !fEnclosuresView->fList->IsFocus())); 830 be_clipboard->Unlock(); 831 832 fQuote->SetEnabled(false); 833 fRemoveQuote->SetEnabled(false); 834 835 fAdd->SetEnabled(true); 836 fRemove->SetEnabled(fEnclosuresView != NULL 837 && fEnclosuresView->fList->CurrentSelection() >= 0); 838 } else { 839 if (fResending) { 840 enable = strlen(fHeaderView->fTo->Text()); 841 fSendNow->SetEnabled(enable); 842 // fSendLater->SetEnabled(enable); 843 844 if (fHeaderView->fTo->HasFocus()) { 845 textView = fHeaderView->fTo->TextView(); 846 textView->GetSelection(&start, &finish); 847 848 fCut->SetEnabled(start != finish); 849 be_clipboard->Lock(); 850 fPaste->SetEnabled(be_clipboard->Data()->HasData("text/plain", B_MIME_TYPE)); 851 be_clipboard->Unlock(); 852 } else { 853 fCut->SetEnabled(false); 854 fPaste->SetEnabled(false); 855 } 856 } else { 857 fCut->SetEnabled(false); 858 fPaste->SetEnabled(false); 859 860 if (!fTrackerMessenger.IsValid()) { 861 fNextMsg->SetEnabled(false); 862 fPrevMsg->SetEnabled(false); 863 } 864 } 865 } 866 867 fPrint->SetEnabled(fContentView->fTextView->TextLength()); 868 869 textView = dynamic_cast<BTextView *>(CurrentFocus()); 870 if (textView != NULL 871 && dynamic_cast<TTextControl *>(textView->Parent()) != NULL) { 872 // one of To:, Subject:, Account:, Cc:, Bcc: 873 textView->GetSelection(&start, &finish); 874 } else if (fContentView->fTextView->IsFocus()) { 875 fContentView->fTextView->GetSelection(&start, &finish); 876 if (!fIncoming) { 877 fQuote->SetEnabled(true); 878 fRemoveQuote->SetEnabled(true); 879 } 880 } 881 882 fCopy->SetEnabled(start != finish); 883 if (!fIncoming) 884 fCut->SetEnabled(start != finish); 885 886 // Undo stuff 887 bool isRedo = false; 888 undo_state undoState = B_UNDO_UNAVAILABLE; 889 890 BTextView *focusTextView = dynamic_cast<BTextView *>(CurrentFocus()); 891 if (focusTextView != NULL) 892 undoState = focusTextView->UndoState(&isRedo); 893 894 // fUndo->SetLabel((isRedo) ? kRedoStrings[undoState] : kUndoStrings[undoState]); 895 fUndo->SetEnabled(undoState != B_UNDO_UNAVAILABLE); 896 } 897 898 899 void 900 TMailWindow::MessageReceived(BMessage *msg) 901 { 902 switch (msg->what) { 903 case FIELD_CHANGED: 904 { 905 int32 prevState = fFieldState, fieldMask = msg->FindInt32("bitmask"); 906 void *source; 907 908 if (msg->FindPointer("source", &source) == B_OK) { 909 int32 length; 910 911 if (fieldMask == FIELD_BODY) 912 length = ((TTextView *)source)->TextLength(); 913 else 914 length = ((BComboBox *)source)->TextView()->TextLength(); 915 916 if (length) 917 fFieldState |= fieldMask; 918 else 919 fFieldState &= ~fieldMask; 920 } 921 922 // Has anything changed? 923 if (prevState != fFieldState || !fChanged) { 924 // Change Buttons to reflect this 925 if (fSaveButton) 926 fSaveButton->SetEnabled(fFieldState); 927 if (fPrintButton) 928 fPrintButton->SetEnabled(fFieldState); 929 if (fSendButton) 930 fSendButton->SetEnabled((fFieldState & FIELD_TO) || (fFieldState & FIELD_BCC)); 931 } 932 fChanged = true; 933 934 // Update title bar if "subject" has changed 935 if (!fIncoming && fieldMask & FIELD_SUBJECT) { 936 // If no subject, set to "Mail" 937 if (!fHeaderView->fSubject->TextView()->TextLength()) 938 SetTitle("Mail"); 939 else 940 SetTitle(fHeaderView->fSubject->Text()); 941 } 942 break; 943 } 944 case LIST_INVOKED: 945 PostMessage(msg, fEnclosuresView); 946 break; 947 948 case CHANGE_FONT: 949 PostMessage(msg, fContentView); 950 break; 951 952 case M_NEW: 953 { 954 BMessage message(M_NEW); 955 message.AddInt32("type", msg->what); 956 be_app->PostMessage(&message); 957 break; 958 } 959 960 case M_SPAM_BUTTON: 961 { 962 /* 963 A popup from a button is good only when the behavior has some consistency and 964 there is some visual indication that a menu will be shown when clicked. A 965 workable implementation would have an extra button attached to the main one 966 which has a downward-pointing arrow. Mozilla Thunderbird's 'Get Mail' button 967 is a good example of this. 968 969 TODO: Replace this code with a split toolbar button 970 */ 971 uint32 buttons; 972 if (msg->FindInt32("buttons", (int32 *)&buttons) == B_OK 973 && buttons == B_SECONDARY_MOUSE_BUTTON) { 974 BPopUpMenu menu("Spam Actions", false, false); 975 for (int i = 0; i < 4; i++) 976 menu.AddItem(new BMenuItem(kSpamMenuItemTextArray[i], new BMessage(M_TRAIN_SPAM_AND_DELETE + i))); 977 978 BPoint where; 979 msg->FindPoint("where", &where); 980 BMenuItem *item; 981 if ((item = menu.Go(where, false, false)) != NULL) 982 PostMessage(item->Message()); 983 break; 984 } else { 985 // Default action for left clicking on the spam button. 986 PostMessage(new BMessage(M_TRAIN_SPAM_AND_DELETE)); 987 } 988 break; 989 } 990 991 case M_TRAIN_SPAM_AND_DELETE: 992 PostMessage(M_DELETE_NEXT); 993 case M_TRAIN_SPAM: 994 TrainMessageAs("Spam"); 995 break; 996 997 case M_UNTRAIN: 998 TrainMessageAs("Uncertain"); 999 break; 1000 1001 case M_TRAIN_GENUINE: 1002 TrainMessageAs("Genuine"); 1003 break; 1004 1005 case M_REPLY: 1006 { 1007 // TODO: This needs removed in favor of a split toolbar button. See comments for Spam button 1008 uint32 buttons; 1009 if (msg->FindInt32("buttons", (int32 *)&buttons) == B_OK 1010 && buttons == B_SECONDARY_MOUSE_BUTTON) { 1011 BPopUpMenu menu("Reply To", false, false); 1012 menu.AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Reply","R) 返信"),new BMessage(M_REPLY))); 1013 menu.AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Reply to Sender","S) 送信者に返信"),new BMessage(M_REPLY_TO_SENDER))); 1014 menu.AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Reply to All","P) 全員に返信"),new BMessage(M_REPLY_ALL))); 1015 1016 BPoint where; 1017 msg->FindPoint("where", &where); 1018 1019 BMenuItem *item; 1020 if ((item = menu.Go(where, false, false)) != NULL) { 1021 item->SetTarget(this); 1022 PostMessage(item->Message()); 1023 } 1024 break; 1025 } 1026 // Fall through 1027 } 1028 case M_FORWARD: 1029 { 1030 // TODO: This needs removed in favor of a split toolbar button. See comments for Spam button 1031 uint32 buttons; 1032 if (msg->FindInt32("buttons", (int32 *)&buttons) == B_OK 1033 && buttons == B_SECONDARY_MOUSE_BUTTON) { 1034 BPopUpMenu menu("Forward", false, false); 1035 menu.AddItem(new BMenuItem(MDR_DIALECT_CHOICE("Forward", "J) 転送"), 1036 new BMessage(M_FORWARD))); 1037 menu.AddItem(new BMenuItem(MDR_DIALECT_CHOICE("Forward without Attachments", 1038 "The opposite: F) 添付ファイルを含む転送"), 1039 new BMessage(M_FORWARD_WITHOUT_ATTACHMENTS))); 1040 1041 BPoint where; 1042 msg->FindPoint("where", &where); 1043 1044 BMenuItem *item; 1045 if ((item = menu.Go(where, false, false)) != NULL) { 1046 item->SetTarget(this); 1047 PostMessage(item->Message()); 1048 } 1049 break; 1050 } 1051 } 1052 1053 // Fall Through 1054 case M_REPLY_ALL: 1055 case M_REPLY_TO_SENDER: 1056 case M_FORWARD_WITHOUT_ATTACHMENTS: 1057 case M_RESEND: 1058 case M_COPY_TO_NEW: 1059 { 1060 BMessage message(M_NEW); 1061 message.AddRef("ref", fRef); 1062 message.AddPointer("window", this); 1063 message.AddInt32("type", msg->what); 1064 be_app->PostMessage(&message); 1065 break; 1066 } 1067 case M_DELETE: 1068 case M_DELETE_PREV: 1069 case M_DELETE_NEXT: 1070 { 1071 if (msg->what == M_DELETE_NEXT && (modifiers() & B_SHIFT_KEY)) 1072 msg->what = M_DELETE_PREV; 1073 1074 bool foundRef = false; 1075 entry_ref nextRef; 1076 if ((msg->what == M_DELETE_PREV || msg->what == M_DELETE_NEXT) && fRef) { 1077 // Find the next message that should be displayed 1078 nextRef = *fRef; 1079 foundRef = GetTrackerWindowFile(&nextRef, msg->what == 1080 M_DELETE_NEXT); 1081 } 1082 if (fIncoming) 1083 SetCurrentMessageRead(); 1084 1085 if (!fTrackerMessenger.IsValid() || !fIncoming) { 1086 // Not associated with a tracker window. Create a new 1087 // messenger and ask the tracker to delete this entry 1088 if (fDraft || fIncoming) { 1089 BMessenger tracker("application/x-vnd.Be-TRAK"); 1090 if (tracker.IsValid()) { 1091 BMessage msg('Ttrs'); 1092 msg.AddRef("refs", fRef); 1093 tracker.SendMessage(&msg); 1094 } else { 1095 (new BAlert("", 1096 MDR_DIALECT_CHOICE ( "Need tracker to move items to trash", 1097 "削除するにはTrackerが必要です。"), 1098 MDR_DIALECT_CHOICE ("sorry","削除できませんでした。")))->Go(); 1099 } 1100 } 1101 } else { 1102 // This is associated with a tracker window. Ask the 1103 // window to delete this entry. Do it this way if we 1104 // can instead of the above way because it doesn't reset 1105 // the selection (even though we set selection below, this 1106 // still causes problems). 1107 BMessage delmsg(B_DELETE_PROPERTY); 1108 BMessage entryspec('sref'); 1109 entryspec.AddRef("refs", fRef); 1110 entryspec.AddString("property", "Entry"); 1111 delmsg.AddSpecifier(&entryspec); 1112 fTrackerMessenger.SendMessage(&delmsg); 1113 } 1114 1115 // If the next file was found, open it. If it was not, 1116 // we have no choice but to close this window. 1117 if (foundRef) { 1118 TMailWindow *window = static_cast<TMailApp *>(be_app)->FindWindow(nextRef); 1119 if (window == NULL) 1120 OpenMessage(&nextRef, fHeaderView->fCharacterSetUserSees); 1121 else 1122 window->Activate(); 1123 1124 SetTrackerSelectionToCurrent(); 1125 1126 if (window == NULL) 1127 break; 1128 } 1129 1130 fSent = true; 1131 BMessage msg(B_CLOSE_REQUESTED); 1132 PostMessage(&msg); 1133 break; 1134 } 1135 1136 case M_CLOSE_READ: 1137 { 1138 BMessage message(B_CLOSE_REQUESTED); 1139 message.AddString("status", "Read"); 1140 PostMessage(&message); 1141 break; 1142 } 1143 case M_CLOSE_SAVED: 1144 { 1145 BMessage message(B_QUIT_REQUESTED); 1146 message.AddString("status", "Saved"); 1147 PostMessage(&message); 1148 break; 1149 } 1150 case kMsgQuitAndKeepAllStatus: 1151 sKeepStatusOnQuit = true; 1152 be_app->PostMessage(B_QUIT_REQUESTED); 1153 break; 1154 case M_CLOSE_SAME: 1155 { 1156 BMessage message(B_QUIT_REQUESTED); 1157 message.AddString("status", ""); 1158 message.AddString("same", ""); 1159 PostMessage(&message); 1160 break; 1161 } 1162 case M_CLOSE_CUSTOM: 1163 if (msg->HasString("status")) { 1164 const char *str; 1165 msg->FindString("status", (const char**) &str); 1166 BMessage message(B_CLOSE_REQUESTED); 1167 message.AddString("status", str); 1168 PostMessage(&message); 1169 } else { 1170 BRect r = Frame(); 1171 r.left += ((r.Width() - STATUS_WIDTH) / 2); 1172 r.right = r.left + STATUS_WIDTH; 1173 r.top += 40; 1174 r.bottom = r.top + STATUS_HEIGHT; 1175 1176 BString string = "could not read"; 1177 BNode node(fRef); 1178 if (node.InitCheck() == B_OK) 1179 ReadAttrString(&node, B_MAIL_ATTR_STATUS, &string); 1180 1181 new TStatusWindow(r, this, string.String()); 1182 } 1183 break; 1184 1185 case M_STATUS: 1186 { 1187 const char* attribute; 1188 if (msg->FindString("attribute", &attribute) != B_OK) 1189 break; 1190 1191 BMessage message(B_CLOSE_REQUESTED); 1192 message.AddString("status", attribute); 1193 PostMessage(&message); 1194 break; 1195 } 1196 case M_HEADER: 1197 { 1198 bool showHeader = !fHeader->IsMarked(); 1199 fHeader->SetMarked(showHeader); 1200 1201 BMessage message(M_HEADER); 1202 message.AddBool("header", showHeader); 1203 PostMessage(&message, fContentView->fTextView); 1204 break; 1205 } 1206 case M_RAW: 1207 { 1208 bool raw = !(fRaw->IsMarked()); 1209 fRaw->SetMarked(raw); 1210 BMessage message(M_RAW); 1211 message.AddBool("raw", raw); 1212 PostMessage(&message, fContentView->fTextView); 1213 break; 1214 } 1215 case M_SEND_NOW: 1216 case M_SAVE_AS_DRAFT: 1217 Send(msg->what == M_SEND_NOW); 1218 break; 1219 1220 case M_SAVE: 1221 { 1222 char *str; 1223 if (msg->FindString("address", (const char **)&str) == B_NO_ERROR) 1224 { 1225 char *arg = (char *)malloc(strlen("META:email ") + strlen(str) + 1); 1226 BVolumeRoster volumeRoster; 1227 BVolume volume; 1228 volumeRoster.GetBootVolume(&volume); 1229 1230 BQuery query; 1231 query.SetVolume(&volume); 1232 sprintf(arg, "META:email=%s", str); 1233 query.SetPredicate(arg); 1234 query.Fetch(); 1235 1236 BEntry entry; 1237 if (query.GetNextEntry(&entry) == B_NO_ERROR) 1238 { 1239 BMessenger tracker("application/x-vnd.Be-TRAK"); 1240 if (tracker.IsValid()) 1241 { 1242 entry_ref ref; 1243 entry.GetRef(&ref); 1244 1245 BMessage open(B_REFS_RECEIVED); 1246 open.AddRef("refs", &ref); 1247 tracker.SendMessage(&open); 1248 } 1249 } 1250 else 1251 { 1252 sprintf(arg, "META:email %s", str); 1253 status_t result = be_roster->Launch("application/x-person", 1, &arg); 1254 if (result != B_NO_ERROR) 1255 (new BAlert("", MDR_DIALECT_CHOICE ( 1256 "Sorry, could not find an application that supports the 'Person' data type.", 1257 "Peopleデータ形式をサポートするアプリケーションが見つかりませんでした。"), 1258 MDR_DIALECT_CHOICE ("OK","了解")))->Go(); 1259 } 1260 free(arg); 1261 } 1262 break; 1263 } 1264 1265 case M_PRINT_SETUP: 1266 PrintSetup(); 1267 break; 1268 1269 case M_PRINT: 1270 Print(); 1271 break; 1272 1273 case M_SELECT: 1274 break; 1275 1276 case M_FIND: 1277 FindWindow::Find(this); 1278 break; 1279 1280 case M_FIND_AGAIN: 1281 FindWindow::FindAgain(this); 1282 break; 1283 1284 case M_QUOTE: 1285 case M_REMOVE_QUOTE: 1286 PostMessage(msg->what, fContentView); 1287 break; 1288 1289 case M_RANDOM_SIG: 1290 { 1291 BList sigList; 1292 BMessage *message; 1293 1294 BVolume volume; 1295 BVolumeRoster().GetBootVolume(&volume); 1296 1297 BQuery query; 1298 query.SetVolume(&volume); 1299 1300 char predicate[128]; 1301 sprintf(predicate, "%s = *", INDEX_SIGNATURE); 1302 query.SetPredicate(predicate); 1303 query.Fetch(); 1304 1305 BEntry entry; 1306 while (query.GetNextEntry(&entry) == B_NO_ERROR) 1307 { 1308 BFile file(&entry, O_RDONLY); 1309 if (file.InitCheck() == B_NO_ERROR) 1310 { 1311 entry_ref ref; 1312 entry.GetRef(&ref); 1313 1314 message = new BMessage(M_SIGNATURE); 1315 message->AddRef("ref", &ref); 1316 sigList.AddItem(message); 1317 } 1318 } 1319 if (sigList.CountItems() > 0) 1320 { 1321 srand(time(0)); 1322 PostMessage((BMessage *)sigList.ItemAt(rand() % sigList.CountItems())); 1323 1324 for (int32 i = 0; (message = (BMessage *)sigList.ItemAt(i)) != NULL; i++) 1325 delete message; 1326 } 1327 break; 1328 } 1329 case M_SIGNATURE: 1330 { 1331 BMessage message(*msg); 1332 PostMessage(&message, fContentView); 1333 fSigAdded = true; 1334 break; 1335 } 1336 case M_SIG_MENU: 1337 { 1338 TMenu *menu; 1339 BMenuItem *item; 1340 menu = new TMenu( "Add Signature", INDEX_SIGNATURE, M_SIGNATURE, true ); 1341 1342 BPoint where; 1343 bool open_anyway = true; 1344 1345 if (msg->FindPoint("where", &where) != B_OK) 1346 { 1347 BRect bounds; 1348 bounds = fSigButton->Bounds(); 1349 where = fSigButton->ConvertToScreen(BPoint((bounds.right-bounds.left)/2, 1350 (bounds.bottom-bounds.top)/2)); 1351 } 1352 else if (msg->FindInt32("buttons") == B_SECONDARY_MOUSE_BUTTON) 1353 open_anyway = false; 1354 1355 if ((item = menu->Go(where, false, open_anyway)) != NULL) 1356 { 1357 item->SetTarget(this); 1358 (dynamic_cast<BInvoker *>(item))->Invoke(); 1359 } 1360 delete menu; 1361 break; 1362 } 1363 1364 case M_ADD: 1365 if (!fPanel) { 1366 BMessenger me(this); 1367 BMessage msg(REFS_RECEIVED); 1368 fPanel = new BFilePanel(B_OPEN_PANEL, &me, &fOpenFolder, false, true, &msg); 1369 } 1370 else if (!fPanel->Window()->IsHidden()) 1371 fPanel->Window()->Activate(); 1372 1373 if (fPanel->Window()->IsHidden()) 1374 fPanel->Window()->Show(); 1375 break; 1376 1377 case M_REMOVE: 1378 PostMessage(msg->what, fEnclosuresView); 1379 break; 1380 1381 case CHARSET_CHOICE_MADE: 1382 if (fIncoming && !fResending) { 1383 // The user wants to see the message they are reading (not 1384 // composing) displayed with a different kind of character set 1385 // for decoding. Reload the whole message and redisplay. For 1386 // messages which are being composed, the character set is 1387 // retrieved from the header view when it is needed. 1388 1389 entry_ref fileRef = *fRef; 1390 int32 characterSet; 1391 msg->FindInt32("charset", &characterSet); 1392 OpenMessage(&fileRef, characterSet); 1393 } 1394 break; 1395 1396 case REFS_RECEIVED: 1397 AddEnclosure(msg); 1398 break; 1399 1400 // 1401 // Navigation Messages 1402 // 1403 case M_PREVMSG: 1404 case M_NEXTMSG: 1405 if (fRef) 1406 { 1407 entry_ref nextRef = *fRef; 1408 if (GetTrackerWindowFile(&nextRef, (msg->what == M_NEXTMSG))) { 1409 TMailWindow *window = static_cast<TMailApp *>(be_app)->FindWindow(nextRef); 1410 if (window == NULL) { 1411 SetCurrentMessageRead(); 1412 OpenMessage(&nextRef, fHeaderView->fCharacterSetUserSees); 1413 } else { 1414 window->Activate(); 1415 1416 //fSent = true; 1417 BMessage msg(B_CLOSE_REQUESTED); 1418 PostMessage(&msg); 1419 } 1420 1421 SetTrackerSelectionToCurrent(); 1422 } 1423 else 1424 beep(); 1425 } 1426 break; 1427 case M_SAVE_POSITION: 1428 if (fRef) 1429 SaveTrackerPosition(fRef); 1430 break; 1431 1432 case M_OPEN_MAIL_FOLDER: 1433 case M_OPEN_MAIL_BOX: 1434 { 1435 BEntry folderEntry; 1436 BPath path; 1437 // Get the user home directory 1438 if (find_directory(B_USER_DIRECTORY, &path) != B_OK) 1439 break; 1440 if (msg->what == M_OPEN_MAIL_FOLDER) 1441 path.Append(kMailFolder); 1442 else 1443 path.Append(kMailboxFolder); 1444 if (folderEntry.SetTo(path.Path()) == B_OK && folderEntry.Exists()) 1445 { 1446 BMessage thePackage(B_REFS_RECEIVED); 1447 BMessenger tracker("application/x-vnd.Be-TRAK"); 1448 1449 entry_ref ref; 1450 folderEntry.GetRef(&ref); 1451 thePackage.AddRef("refs", &ref); 1452 tracker.SendMessage(&thePackage); 1453 } 1454 break; 1455 } 1456 case RESET_BUTTONS: 1457 fChanged = false; 1458 fFieldState = 0; 1459 if (fHeaderView->fTo->TextView()->TextLength()) 1460 fFieldState |= FIELD_TO; 1461 if (fHeaderView->fSubject->TextView()->TextLength()) 1462 fFieldState |= FIELD_SUBJECT; 1463 if (fHeaderView->fCc->TextView()->TextLength()) 1464 fFieldState |= FIELD_CC; 1465 if (fHeaderView->fBcc->TextView()->TextLength()) 1466 fFieldState |= FIELD_BCC; 1467 if (fContentView->fTextView->TextLength()) 1468 fFieldState |= FIELD_BODY; 1469 1470 if (fSaveButton) 1471 fSaveButton->SetEnabled(false); 1472 if (fPrintButton) 1473 fPrintButton->SetEnabled(fFieldState); 1474 if (fSendButton) 1475 fSendButton->SetEnabled((fFieldState & FIELD_TO) || (fFieldState & FIELD_BCC)); 1476 break; 1477 1478 case M_CHECK_SPELLING: 1479 if (gDictCount == 0) 1480 // Give the application time to initialise and load the dictionaries. 1481 snooze (1500000); 1482 if (!gDictCount) 1483 { 1484 beep(); 1485 (new BAlert("", 1486 MDR_DIALECT_CHOICE ( 1487 "The spell check feature requires the optional \"words\" file on your BeOS CD.", 1488 "スペルチェク機能はBeOS CDの optional \"words\" ファイルが必要です"), 1489 MDR_DIALECT_CHOICE ("OK","了解"), 1490 NULL, NULL, B_WIDTH_AS_USUAL, B_OFFSET_SPACING, 1491 B_STOP_ALERT))->Go(); 1492 } 1493 else 1494 { 1495 fSpelling->SetMarked(!fSpelling->IsMarked()); 1496 fContentView->fTextView->EnableSpellCheck(fSpelling->IsMarked()); 1497 } 1498 break; 1499 1500 case M_EDIT_QUERIES: 1501 { 1502 BPath path; 1503 if (_GetQueryPath(&path) < B_OK) 1504 break; 1505 1506 // the user used this command, make sure the folder actually 1507 // exists - if it didn't inform the user what to do with it 1508 BEntry entry(path.Path()); 1509 bool showAlert = false; 1510 if (!entry.Exists()) { 1511 showAlert = true; 1512 create_directory(path.Path(), 0777); 1513 } 1514 1515 BEntry folderEntry; 1516 if (folderEntry.SetTo(path.Path()) == B_OK 1517 && folderEntry.Exists()) { 1518 BMessage openFolderCommand(B_REFS_RECEIVED); 1519 BMessenger tracker("application/x-vnd.Be-TRAK"); 1520 1521 entry_ref ref; 1522 folderEntry.GetRef(&ref); 1523 openFolderCommand.AddRef("refs", &ref); 1524 tracker.SendMessage(&openFolderCommand); 1525 } 1526 1527 if (showAlert) { 1528 // just some patience before Tracker pops up the folder 1529 snooze(250000); 1530 BAlert* alert = new BAlert("helpful message", 1531 "Put your favorite e-mail queries and query " 1532 "templates in this folder.", 1533 "Ok", NULL, NULL, B_WIDTH_AS_USUAL, B_IDEA_ALERT); 1534 alert->Go(NULL); 1535 } 1536 1537 break; 1538 } 1539 #ifdef __HAIKU__ 1540 case B_PATH_MONITOR: 1541 _RebuildQueryMenu(); 1542 break; 1543 #endif 1544 1545 default: 1546 BWindow::MessageReceived(msg); 1547 } 1548 } 1549 1550 1551 void 1552 TMailWindow::AddEnclosure(BMessage *msg) 1553 { 1554 if (fEnclosuresView == NULL && !fIncoming) { 1555 BRect r; 1556 r.left = 0; 1557 r.top = fHeaderView->Frame().bottom - 1; 1558 r.right = Frame().Width() + 2; 1559 r.bottom = r.top + ENCLOSURES_HEIGHT; 1560 1561 fEnclosuresView = new TEnclosuresView(r, Frame()); 1562 AddChild(fEnclosuresView, fContentView); 1563 fContentView->ResizeBy(0, -ENCLOSURES_HEIGHT); 1564 fContentView->MoveBy(0, ENCLOSURES_HEIGHT); 1565 } 1566 1567 if (fEnclosuresView == NULL) 1568 return; 1569 1570 if (msg && msg->HasRef("refs")) { 1571 // Add enclosure to view 1572 PostMessage(msg, fEnclosuresView); 1573 1574 fChanged = true; 1575 BEntry entry; 1576 entry_ref ref; 1577 msg->FindRef("refs", &ref); 1578 entry.SetTo(&ref); 1579 entry.GetParent(&entry); 1580 entry.GetRef(&fOpenFolder); 1581 } 1582 } 1583 1584 1585 bool 1586 TMailWindow::QuitRequested() 1587 { 1588 int32 result; 1589 1590 if ((!fIncoming || (fIncoming && fResending)) && fChanged && !fSent 1591 && (strlen(fHeaderView->fTo->Text()) 1592 || strlen(fHeaderView->fSubject->Text()) 1593 || (fHeaderView->fCc && strlen(fHeaderView->fCc->Text())) 1594 || (fHeaderView->fBcc && strlen(fHeaderView->fBcc->Text())) 1595 || (strlen(fContentView->fTextView->Text()) && (!fStartingText || fStartingText && strcmp(fContentView->fTextView->Text(), fStartingText))) 1596 || (fEnclosuresView != NULL && fEnclosuresView->fList->CountItems()))) 1597 { 1598 if (fResending) { 1599 BAlert *alert = new BAlert("", 1600 MDR_DIALECT_CHOICE ( 1601 "Do you wish to send this message before closing?", 1602 "閉じる前に送信しますか?"), 1603 MDR_DIALECT_CHOICE ("Discard","無視"), 1604 MDR_DIALECT_CHOICE ("Cancel","中止"), 1605 MDR_DIALECT_CHOICE ("Send","送信"), 1606 B_WIDTH_AS_USUAL, B_OFFSET_SPACING, 1607 B_WARNING_ALERT); 1608 alert->SetShortcut(0,'d'); 1609 alert->SetShortcut(1,B_ESCAPE); 1610 result = alert->Go(); 1611 1612 switch (result) { 1613 case 0: // Discard 1614 break; 1615 case 1: // Cancel 1616 return false; 1617 case 2: // Send 1618 Send(true); 1619 break; 1620 } 1621 } else { 1622 BAlert *alert = new BAlert("", 1623 MDR_DIALECT_CHOICE ( 1624 "Do you wish to save this message as a draft before closing?", 1625 "閉じる前に保存しますか?"), 1626 MDR_DIALECT_CHOICE ("Don't Save","保存しない"), 1627 MDR_DIALECT_CHOICE ("Cancel","中止"), 1628 MDR_DIALECT_CHOICE ("Save","保存"), 1629 B_WIDTH_AS_USUAL, B_OFFSET_SPACING, 1630 B_WARNING_ALERT); 1631 alert->SetShortcut(0,'d'); 1632 alert->SetShortcut(1,B_ESCAPE); 1633 result = alert->Go(); 1634 switch (result) { 1635 case 0: // Don't Save 1636 break; 1637 case 1: // Cancel 1638 return false; 1639 case 2: // Save 1640 Send(false); 1641 break; 1642 } 1643 } 1644 } 1645 1646 BMessage message(WINDOW_CLOSED); 1647 message.AddInt32("kind", MAIL_WINDOW); 1648 message.AddPointer("window", this); 1649 be_app->PostMessage(&message); 1650 1651 if (CurrentMessage() && CurrentMessage()->HasString("status")) { 1652 // User explicitly requests a status to set this message to. 1653 if (!CurrentMessage()->HasString("same")) { 1654 const char *status = CurrentMessage()->FindString("status"); 1655 if (status != NULL) { 1656 BNode node(fRef); 1657 if (node.InitCheck() == B_NO_ERROR) { 1658 node.RemoveAttr(B_MAIL_ATTR_STATUS); 1659 WriteAttrString(&node, B_MAIL_ATTR_STATUS, status); 1660 } 1661 } 1662 } 1663 } else if (fRef && !sKeepStatusOnQuit) { 1664 // ...Otherwise just set the message read 1665 SetCurrentMessageRead(); 1666 } 1667 1668 #ifdef __HAIKU__ 1669 BPrivate::BPathMonitor::StopWatching(BMessenger(this, this)); 1670 #endif 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("", 2179 MDR_DIALECT_CHOICE ("E-mail draft could not be saved!","ドラフトは保存できませんでした。"), 2180 MDR_DIALECT_CHOICE ("OK","了解")))->Go(); 2181 } 2182 return status; 2183 } 2184 2185 Hide(); 2186 // depending on the system (and I/O) load, this could take a while 2187 // but the user shouldn't be left waiting 2188 2189 if (fHeaderView != NULL) 2190 characterSetToUse = fHeaderView->fCharacterSetUserSees; 2191 2192 // Set up the encoding to use for converting binary to printable ASCII. 2193 // Normally this will be quoted printable, but for some old software, 2194 // particularly Japanese stuff, they only understand base64. They also 2195 // prefer it for the smaller size. Later on this will be reduced to 7bit 2196 // if the encoded text is just 7bit characters. 2197 if (characterSetToUse == B_SJIS_CONVERSION 2198 || characterSetToUse == B_EUC_CONVERSION) 2199 encodingForBody = base64; 2200 else if (characterSetToUse == B_JIS_CONVERSION 2201 || characterSetToUse == B_MAIL_US_ASCII_CONVERSION 2202 || characterSetToUse == B_ISO1_CONVERSION 2203 || characterSetToUse == B_EUC_KR_CONVERSION) 2204 encodingForBody = eight_bit; 2205 2206 // Using quoted printable headers on almost completely non-ASCII Japanese 2207 // is a waste of time. Besides, some stupid cell phone services need 2208 // base64 in the headers. 2209 if (characterSetToUse == B_SJIS_CONVERSION 2210 || characterSetToUse == B_EUC_CONVERSION 2211 || characterSetToUse == B_JIS_CONVERSION 2212 || characterSetToUse == B_EUC_KR_CONVERSION) 2213 encodingForHeaders = base64; 2214 2215 // Count the number of characters in the message body which aren't in the 2216 // currently selected character set. Also see if the resulting encoded 2217 // text can safely use 7 bit characters. 2218 if (fContentView->fTextView->TextLength() > 0) { 2219 // First do a trial encoding with the user's character set. 2220 int32 converterState = 0; 2221 int32 originalLength; 2222 BString tempString; 2223 int32 tempStringLength; 2224 char* tempStringPntr; 2225 originalLength = fContentView->fTextView->TextLength(); 2226 tempStringLength = originalLength * 2227 6 /* Some character sets bloat up on escape codes */; 2228 tempStringPntr = tempString.LockBuffer (tempStringLength); 2229 if (tempStringPntr != NULL && mail_convert_from_utf8(characterSetToUse, 2230 fContentView->fTextView->Text(), &originalLength, 2231 tempStringPntr, &tempStringLength, &converterState, 2232 0x1A /* used for unknown characters */) == B_OK) { 2233 // Check for any characters which don't fit in a 7 bit encoding. 2234 int i; 2235 bool has8Bit = false; 2236 for (i = 0; i < tempStringLength; i++) 2237 if (tempString[i] == 0 || (tempString[i] & 0x80)) { 2238 has8Bit = true; 2239 break; 2240 } 2241 if (!has8Bit) 2242 encodingForBody = seven_bit; 2243 tempString.UnlockBuffer (tempStringLength); 2244 2245 // Count up the number of unencoded characters and warn the user about them. 2246 if (fApp->WarnAboutUnencodableCharacters()) { 2247 int32 offset = 0; 2248 int count = 0; 2249 while (offset >= 0) { 2250 offset = tempString.FindFirst (0x1A, offset); 2251 if (offset >= 0) { 2252 count++; 2253 offset++; // Don't get stuck finding the same character again. 2254 } 2255 } 2256 if (count > 0) { 2257 int32 userAnswer; 2258 BString messageString; 2259 MDR_DIALECT_CHOICE ( 2260 messageString << "Your main text contains " << count << 2261 " unencodable characters. Perhaps a different character " 2262 "set would work better? Hit Send to send it 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 , 2267 messageString << "送信メールの本文には " << count << 2268 " 個のエンコードできない文字があります。" 2269 "違う文字セットを使うほうがよい可能性があります。" 2270 "このまま送信の場合は「送信」ボタンを押してください。" 2271 "その場合、代用文字がUnicode化可能な文字に代わって使われます。" 2272 "文字セットを変更する場合は「中止」ボタンを押して下さい。" 2273 ); 2274 userAnswer = (new BAlert ("Question", messageString.String(), 2275 MDR_DIALECT_CHOICE ("Send","送信"), 2276 MDR_DIALECT_CHOICE ("Cancel","中止"), // Default is cancel. 2277 NULL, B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT)) 2278 ->Go(); 2279 if (userAnswer == 1) 2280 return -1; // Cancel was picked. 2281 } 2282 } 2283 } 2284 } 2285 2286 status_t result; 2287 2288 if (fResending) { 2289 BFile file(fRef, O_RDONLY); 2290 result = file.InitCheck(); 2291 if (result == B_OK) { 2292 BEmailMessage mail(&file); 2293 mail.SetTo(fHeaderView->fTo->Text(), characterSetToUse, encodingForHeaders); 2294 2295 if (fHeaderView->fChain != ~0L) 2296 mail.SendViaAccount(fHeaderView->fChain); 2297 2298 result = mail.Send(now); 2299 } 2300 } else { 2301 if (fMail == NULL) 2302 // the mail will be deleted when the window is closed 2303 fMail = new BEmailMessage; 2304 2305 // Had an embarrassing bug where replying to a message and clearing the 2306 // CC field meant that it got sent out anyway, so pass in empty strings 2307 // when changing the header to force it to remove the header. 2308 2309 fMail->SetTo(fHeaderView->fTo->Text(), characterSetToUse, 2310 encodingForHeaders); 2311 fMail->SetSubject(fHeaderView->fSubject->Text(), characterSetToUse, 2312 encodingForHeaders); 2313 fMail->SetCC(fHeaderView->fCc->Text(), characterSetToUse, 2314 encodingForHeaders); 2315 fMail->SetBCC(fHeaderView->fBcc->Text()); 2316 2317 //--- Add X-Mailer field 2318 { 2319 // get app version 2320 version_info info; 2321 memset(&info, 0, sizeof(version_info)); 2322 2323 app_info appInfo; 2324 if (be_app->GetAppInfo(&appInfo) == B_OK) { 2325 BFile file(&appInfo.ref, B_READ_ONLY); 2326 if (file.InitCheck() == B_OK) { 2327 BAppFileInfo appFileInfo(&file); 2328 if (appFileInfo.InitCheck() == B_OK) 2329 appFileInfo.GetVersionInfo(&info, B_APP_VERSION_KIND); 2330 } 2331 } 2332 2333 char versionString[255]; 2334 sprintf(versionString, 2335 "Mail/Haiku %ld.%ld.%ld", 2336 info.major, info.middle, info.minor); 2337 fMail->SetHeaderField("X-Mailer", versionString); 2338 } 2339 2340 /****/ 2341 2342 // the content text is always added to make sure there is a mail body 2343 fMail->SetBodyTextTo(""); 2344 fContentView->fTextView->AddAsContent(fMail, fApp->WrapMode(), 2345 characterSetToUse, encodingForBody); 2346 2347 if (fEnclosuresView != NULL) { 2348 TListItem *item; 2349 int32 index = 0; 2350 while ((item = (TListItem *)fEnclosuresView->fList->ItemAt(index++)) != NULL) { 2351 if (item->Component()) 2352 continue; 2353 2354 // leave out missing enclosures 2355 BEntry entry(item->Ref()); 2356 if (!entry.Exists()) 2357 continue; 2358 2359 fMail->Attach(item->Ref(), fApp->AttachAttributes()); 2360 } 2361 } 2362 if (fHeaderView->fChain != ~0L) 2363 fMail->SendViaAccount(fHeaderView->fChain); 2364 2365 result = fMail->Send(now); 2366 2367 if (fReplying) { 2368 // Set status of the replied mail 2369 2370 BNode node(&fRepliedMail); 2371 if (node.InitCheck() >= B_OK) { 2372 if (fOriginatingWindow) { 2373 BMessage msg(M_SAVE_POSITION), reply; 2374 fOriginatingWindow->SendMessage(&msg, &reply); 2375 } 2376 WriteAttrString(&node, B_MAIL_ATTR_STATUS, "Replied"); 2377 } 2378 } 2379 } 2380 2381 bool close = false; 2382 char errorMessage[256]; 2383 2384 switch (result) { 2385 case B_NO_ERROR: 2386 close = true; 2387 fSent = true; 2388 2389 // If it's a draft, remove the draft file 2390 if (fDraft) { 2391 BEntry entry(fRef); 2392 entry.Remove(); 2393 } 2394 break; 2395 2396 case B_MAIL_NO_DAEMON: 2397 { 2398 close = true; 2399 fSent = true; 2400 2401 int32 start = (new BAlert("no daemon", 2402 MDR_DIALECT_CHOICE ("The mail_daemon is not running. " 2403 "The message is queued and will be sent when the mail_daemon is started.", 2404 "mail_daemon が開始されていません " 2405 "このメッセージは処理待ちとなり、mail_daemon 開始後に処理されます"), 2406 MDR_DIALECT_CHOICE ("Start Now","ただちに開始する"), 2407 MDR_DIALECT_CHOICE ("Ok","了解")))->Go(); 2408 2409 if (start == 0) { 2410 result = be_roster->Launch("application/x-vnd.Be-POST"); 2411 if (result == B_OK) 2412 BMailDaemon::SendQueuedMail(); 2413 else { 2414 sprintf(errorMessage,"The mail_daemon could not be started:\n\t%s", 2415 strerror(result)); 2416 } 2417 } 2418 break; 2419 } 2420 2421 // case B_MAIL_UNKNOWN_HOST: 2422 // case B_MAIL_ACCESS_ERROR: 2423 // sprintf(errorMessage, "An error occurred trying to connect with the SMTP " 2424 // "host. Check your SMTP host name."); 2425 // break; 2426 // 2427 // case B_MAIL_NO_RECIPIENT: 2428 // sprintf(errorMessage, "You must have either a \"To\" or \"Bcc\" recipient."); 2429 // break; 2430 2431 default: 2432 sprintf(errorMessage, "An error occurred trying to send mail:\n\t%s", 2433 strerror(result)); 2434 break; 2435 } 2436 2437 if (result != B_NO_ERROR && result != B_MAIL_NO_DAEMON) { 2438 beep(); 2439 (new BAlert("", errorMessage, "Ok"))->Go(); 2440 } 2441 if (close) 2442 PostMessage(B_QUIT_REQUESTED); 2443 2444 return result; 2445 } 2446 2447 2448 status_t 2449 TMailWindow::SaveAsDraft() 2450 { 2451 status_t status; 2452 BPath draftPath; 2453 BDirectory dir; 2454 BFile draft; 2455 uint32 flags = 0; 2456 2457 if (fDraft) { 2458 if ((status = draft.SetTo(fRef, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE)) != B_OK) 2459 return status; 2460 } else { 2461 // Get the user home directory 2462 if ((status = find_directory(B_USER_DIRECTORY, &draftPath)) != B_OK) 2463 return status; 2464 2465 // Append the relative path of the draft directory 2466 draftPath.Append(kDraftPath); 2467 2468 // Create the file 2469 status = dir.SetTo(draftPath.Path()); 2470 switch (status) { 2471 // Create the directory if it does not exist 2472 case B_ENTRY_NOT_FOUND: 2473 if ((status = dir.CreateDirectory(draftPath.Path(), &dir)) != B_OK) 2474 return status; 2475 case B_OK: 2476 { 2477 char fileName[512], *eofn; 2478 int32 i; 2479 2480 // save as some version of the message's subject 2481 strncpy(fileName, fHeaderView->fSubject->Text(), sizeof(fileName)-10); 2482 fileName[sizeof(fileName)-10]='\0'; // terminate like strncpy doesn't 2483 eofn = fileName + strlen(fileName); 2484 2485 // convert /, \ and : to - 2486 for (char *bad = fileName; (bad = strchr(bad, '/')) != NULL; ++bad) *bad = '-'; 2487 for (char *bad = fileName; (bad = strchr(bad, '\\')) != NULL;++bad) *bad = '-'; 2488 for (char *bad = fileName; (bad = strchr(bad, ':')) != NULL; ++bad) *bad = '-'; 2489 2490 // Create the file; if the name exists, find a unique name 2491 flags = B_WRITE_ONLY | B_CREATE_FILE | B_FAIL_IF_EXISTS; 2492 for (i = 1; (status = draft.SetTo(&dir, fileName, flags )) != B_OK; i++) { 2493 if( status != B_FILE_EXISTS ) 2494 return status; 2495 sprintf(eofn, "%ld", i ); 2496 } 2497 2498 // Cache the ref 2499 delete fRef; 2500 BEntry entry(&dir, fileName); 2501 fRef = new entry_ref; 2502 entry.GetRef(fRef); 2503 break; 2504 } 2505 default: 2506 return status; 2507 } 2508 } 2509 2510 // Write the content of the message 2511 draft.Write(fContentView->fTextView->Text(), fContentView->fTextView->TextLength()); 2512 2513 // 2514 // Add the header stuff as attributes 2515 // 2516 WriteAttrString(&draft, B_MAIL_ATTR_NAME, fHeaderView->fTo->Text()); 2517 WriteAttrString(&draft, B_MAIL_ATTR_TO, fHeaderView->fTo->Text()); 2518 WriteAttrString(&draft, B_MAIL_ATTR_SUBJECT, fHeaderView->fSubject->Text()); 2519 if (fHeaderView->fCc != NULL) 2520 WriteAttrString(&draft, B_MAIL_ATTR_CC, fHeaderView->fCc->Text()); 2521 if (fHeaderView->fBcc != NULL) 2522 WriteAttrString(&draft, "MAIL:bcc", fHeaderView->fBcc->Text()); 2523 2524 // Add the draft attribute for indexing 2525 uint32 draftAttr = true; 2526 draft.WriteAttr( "MAIL:draft", B_INT32_TYPE, 0, &draftAttr, sizeof(uint32) ); 2527 2528 // Add Attachment paths in attribute 2529 if (fEnclosuresView != NULL) { 2530 TListItem *item; 2531 BPath path; 2532 BString pathStr; 2533 2534 for (int32 i = 0; (item = (TListItem *)fEnclosuresView->fList->ItemAt(i)) != NULL; i++) { 2535 if (i > 0) 2536 pathStr.Append(":"); 2537 2538 BEntry entry(item->Ref(), true); 2539 if (!entry.Exists()) 2540 continue; 2541 2542 entry.GetPath(&path); 2543 pathStr.Append(path.Path()); 2544 } 2545 if (pathStr.Length()) 2546 WriteAttrString(&draft, "MAIL:attachments", pathStr.String()); 2547 } 2548 2549 // Set the MIME Type of the file 2550 BNodeInfo info(&draft); 2551 info.SetType(kDraftType); 2552 2553 fDraft = true; 2554 fChanged = false; 2555 2556 return B_OK; 2557 } 2558 2559 2560 status_t 2561 TMailWindow::TrainMessageAs(const char *CommandWord) 2562 { 2563 status_t errorCode = -1; 2564 char errorString[1500]; 2565 BEntry fileEntry; 2566 BPath filePath; 2567 BMessage replyMessage; 2568 BMessage scriptingMessage; 2569 team_id serverTeam; 2570 2571 if (fRef == NULL) 2572 goto ErrorExit; // Need to have a real file and name. 2573 errorCode = fileEntry.SetTo(fRef, true /* traverse */); 2574 if (errorCode != B_OK) 2575 goto ErrorExit; 2576 errorCode = fileEntry.GetPath(&filePath); 2577 if (errorCode != B_OK) 2578 goto ErrorExit; 2579 fileEntry.Unset(); 2580 2581 // Get a connection to the spam database server. Launch if needed. 2582 2583 if (!fMessengerToSpamServer.IsValid()) { 2584 // Make sure the server is running. 2585 if (!be_roster->IsRunning (kSpamServerSignature)) { 2586 errorCode = be_roster->Launch (kSpamServerSignature); 2587 if (errorCode != B_OK) { 2588 BPath path; 2589 entry_ref ref; 2590 directory_which places[] = {B_COMMON_BIN_DIRECTORY,B_BEOS_BIN_DIRECTORY}; 2591 for (int32 i = 0; i < 2; i++) { 2592 find_directory(places[i],&path); 2593 path.Append("spamdbm"); 2594 if (!BEntry(path.Path()).Exists()) 2595 continue; 2596 get_ref_for_path(path.Path(),&ref); 2597 if ((errorCode = be_roster->Launch (&ref)) == B_OK) 2598 break; 2599 } 2600 if (errorCode != B_OK) 2601 goto ErrorExit; 2602 } 2603 } 2604 2605 // Set up the messenger to the database server. 2606 errorCode = B_SERVER_NOT_FOUND; 2607 serverTeam = be_roster->TeamFor(kSpamServerSignature); 2608 if (serverTeam < 0) 2609 goto ErrorExit; 2610 2611 fMessengerToSpamServer = BMessenger (kSpamServerSignature, serverTeam, &errorCode); 2612 if (!fMessengerToSpamServer.IsValid()) 2613 goto ErrorExit; 2614 } 2615 2616 // Ask the server to train on the message. Give it the command word and 2617 // the absolute path name to use. 2618 2619 scriptingMessage.MakeEmpty(); 2620 scriptingMessage.what = B_SET_PROPERTY; 2621 scriptingMessage.AddSpecifier(CommandWord); 2622 errorCode = scriptingMessage.AddData("data", B_STRING_TYPE, 2623 filePath.Path(), strlen(filePath.Path()) + 1, false /* fixed size */); 2624 if (errorCode != B_OK) 2625 goto ErrorExit; 2626 replyMessage.MakeEmpty(); 2627 errorCode = fMessengerToSpamServer.SendMessage(&scriptingMessage, 2628 &replyMessage); 2629 if (errorCode != B_OK 2630 || replyMessage.FindInt32("error", &errorCode) != B_OK 2631 || errorCode != B_OK) 2632 goto ErrorExit; // Classification failed in one of many ways. 2633 2634 SetTitleForMessage(); // Update window title to show new spam classification. 2635 return B_OK; 2636 2637 ErrorExit: 2638 beep(); 2639 sprintf(errorString, "Unable to train the message file \"%s\" as %s. " 2640 "Possibly useful error code: %s (%ld).", 2641 filePath.Path(), CommandWord, strerror (errorCode), errorCode); 2642 (new BAlert("", errorString, 2643 MDR_DIALECT_CHOICE("OK","了解")))->Go(); 2644 return errorCode; 2645 } 2646 2647 2648 void 2649 TMailWindow::SetTitleForMessage() 2650 { 2651 // 2652 // Figure out the title of this message and set the title bar 2653 // 2654 BString title = "Mail"; 2655 2656 if (fIncoming) { 2657 if (fMail->GetName(&title) == B_OK) 2658 title << ": \"" << fMail->Subject() << "\""; 2659 else 2660 title = fMail->Subject(); 2661 2662 if (fApp->ShowSpamGUI() && fRef != NULL) { 2663 BString classification; 2664 BNode node (fRef); 2665 char numberString [30]; 2666 BString oldTitle (title); 2667 float spamRatio; 2668 if (node.InitCheck() != B_OK || node.ReadAttrString 2669 ("MAIL:classification", &classification) != B_OK) 2670 classification = "Unrated"; 2671 if (classification != "Spam" && classification != "Genuine") { 2672 // Uncertain, Unrated and other unknown classes, show the ratio. 2673 if (node.InitCheck() == B_OK && sizeof (spamRatio) == 2674 node.ReadAttr("MAIL:ratio_spam", B_FLOAT_TYPE, 0, 2675 &spamRatio, sizeof (spamRatio))) { 2676 sprintf (numberString, "%.4f", spamRatio); 2677 classification << " " << numberString; 2678 } 2679 } 2680 title = ""; 2681 title << "[" << classification << "] " << oldTitle; 2682 } 2683 } 2684 SetTitle(title.String()); 2685 } 2686 2687 2688 // 2689 // Open *another* message in the existing mail window. Some code here is 2690 // duplicated from various constructors. 2691 // The duplicated code should be in a private initializer method -- axeld. 2692 // 2693 2694 status_t 2695 TMailWindow::OpenMessage(entry_ref *ref, uint32 characterSetForDecoding) 2696 { 2697 // 2698 // Set some references to the email file 2699 // 2700 if (fRef) 2701 delete fRef; 2702 fRef = new entry_ref(*ref); 2703 2704 if (fStartingText) 2705 { 2706 free(fStartingText); 2707 fStartingText = NULL; 2708 } 2709 fPrevTrackerPositionSaved = false; 2710 fNextTrackerPositionSaved = false; 2711 2712 fContentView->fTextView->StopLoad(); 2713 delete fMail; 2714 2715 BFile file(fRef, B_READ_ONLY); 2716 status_t err = file.InitCheck(); 2717 if (err != B_OK) 2718 return err; 2719 2720 char mimeType[256]; 2721 BNodeInfo fileInfo(&file); 2722 fileInfo.GetType(mimeType); 2723 2724 // Check if it's a draft file, which contains only the text, and has the 2725 // from, to, bcc, attachments listed as attributes. 2726 if (!strcmp(kDraftType, mimeType)) 2727 { 2728 BNode node(fRef); 2729 off_t size; 2730 BString string; 2731 2732 fMail = new BEmailMessage; // Not really used much, but still needed. 2733 2734 // Load the raw UTF-8 text from the file. 2735 file.GetSize(&size); 2736 fContentView->fTextView->SetText(&file, 0, size); 2737 2738 // Restore Fields from attributes 2739 if (ReadAttrString(&node, B_MAIL_ATTR_TO, &string) == B_OK) 2740 fHeaderView->fTo->SetText(string.String()); 2741 if (ReadAttrString(&node, B_MAIL_ATTR_SUBJECT, &string) == B_OK) 2742 fHeaderView->fSubject->SetText(string.String()); 2743 if (ReadAttrString(&node, B_MAIL_ATTR_CC, &string) == B_OK) 2744 fHeaderView->fCc->SetText(string.String()); 2745 if (ReadAttrString(&node, "MAIL:bcc", &string) == B_OK) 2746 fHeaderView->fBcc->SetText(string.String()); 2747 2748 // Restore attachments 2749 if (ReadAttrString(&node, "MAIL:attachments", &string) == B_OK) 2750 { 2751 BMessage msg(REFS_RECEIVED); 2752 entry_ref enc_ref; 2753 2754 char *s = strtok((char *)string.String(), ":"); 2755 while (s) 2756 { 2757 BEntry entry(s, true); 2758 if (entry.Exists()) 2759 { 2760 entry.GetRef(&enc_ref); 2761 msg.AddRef("refs", &enc_ref); 2762 } 2763 s = strtok(NULL, ":"); 2764 } 2765 AddEnclosure(&msg); 2766 } 2767 PostMessage(RESET_BUTTONS); 2768 fIncoming = false; 2769 fDraft = true; 2770 } 2771 else // A real mail message, parse its headers to get from, to, etc. 2772 { 2773 fMail = new BEmailMessage(fRef, characterSetForDecoding); 2774 fIncoming = true; 2775 fHeaderView->LoadMessage(fMail); 2776 } 2777 2778 err = fMail->InitCheck(); 2779 if (err < B_OK) 2780 { 2781 delete fMail; 2782 fMail = NULL; 2783 return err; 2784 } 2785 2786 SetTitleForMessage(); 2787 2788 if (fIncoming) 2789 { 2790 // 2791 // Put the addresses in the 'Save Address' Menu 2792 // 2793 BMenuItem *item; 2794 while ((item = fSaveAddrMenu->RemoveItem(0L)) != NULL) 2795 delete item; 2796 2797 // create the list of addresses 2798 2799 BList addressList; 2800 get_address_list(addressList, fMail->To(), extract_address); 2801 get_address_list(addressList, fMail->CC(), extract_address); 2802 get_address_list(addressList, fMail->From(), extract_address); 2803 get_address_list(addressList, fMail->ReplyTo(), extract_address); 2804 2805 BMessage *msg; 2806 2807 for (int32 i = addressList.CountItems(); i-- > 0;) { 2808 char *address = (char *)addressList.RemoveItem(0L); 2809 2810 // insert the new address in alphabetical order 2811 int32 index = 0; 2812 while ((item = fSaveAddrMenu->ItemAt(index)) != NULL) { 2813 if (!strcmp(address, item->Label())) { 2814 // item already in list 2815 goto skip; 2816 } 2817 2818 if (strcmp(address, item->Label()) < 0) 2819 break; 2820 2821 index++; 2822 } 2823 2824 msg = new BMessage(M_SAVE); 2825 msg->AddString("address", address); 2826 fSaveAddrMenu->AddItem(new BMenuItem(address, msg), index); 2827 2828 skip: 2829 free(address); 2830 } 2831 2832 // 2833 // Clear out existing contents of text view. 2834 // 2835 fContentView->fTextView->SetText("", (int32)0); 2836 2837 fContentView->fTextView->LoadMessage(fMail, false, NULL); 2838 } 2839 2840 return B_OK; 2841 } 2842 2843 2844 TMailWindow * 2845 TMailWindow::FrontmostWindow() 2846 { 2847 BAutolock locker(sWindowListLock); 2848 if (sWindowList.CountItems() > 0) 2849 return (TMailWindow *)sWindowList.ItemAt(0); 2850 2851 return NULL; 2852 } 2853 2854 2855 /* 2856 // Copied from src/kits/tracker/FindPanel.cpp. 2857 uint32 2858 TMailWindow::InitialMode(const BNode *node) 2859 { 2860 if (!node || node->InitCheck() != B_OK) 2861 return kByNameItem; 2862 2863 uint32 result; 2864 if (node->ReadAttr(kAttrQueryInitialMode, B_INT32_TYPE, 0, 2865 (int32 *)&result, sizeof(int32)) <= 0) 2866 return kByNameItem; 2867 2868 return result; 2869 } 2870 2871 2872 // Copied from src/kits/tracker/FindPanel.cpp. 2873 int32 2874 TMailWindow::InitialAttrCount(const BNode *node) 2875 { 2876 if (!node || node->InitCheck() != B_OK) 2877 return 1; 2878 2879 int32 result; 2880 if (node->ReadAttr(kAttrQueryInitialNumAttrs, B_INT32_TYPE, 0, 2881 &result, sizeof(int32)) <= 0) 2882 return 1; 2883 2884 return result; 2885 }*/ 2886 2887 2888 // #pragma mark - 2889 2890 2891 void 2892 TMailWindow::_UpdateSizeLimits() 2893 { 2894 float minWidth, maxWidth, minHeight, maxHeight; 2895 GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight); 2896 2897 float height; 2898 fMenuBar->GetPreferredSize(&minWidth, &height); 2899 2900 minHeight = height; 2901 2902 if (fButtonBar) { 2903 fButtonBar->GetPreferredSize(&minWidth, &height); 2904 minHeight += height; 2905 } else { 2906 minWidth = WIND_WIDTH; 2907 } 2908 2909 minHeight += fHeaderView->Bounds().Height() + ENCLOSURES_HEIGHT + 60; 2910 2911 SetSizeLimits(minWidth, RIGHT_BOUNDARY, minHeight, RIGHT_BOUNDARY); 2912 } 2913 2914 2915 status_t 2916 TMailWindow::_GetQueryPath(BPath* queryPath) const 2917 { 2918 // get the user home directory and from there the query folder 2919 status_t ret = find_directory(B_USER_DIRECTORY, queryPath); 2920 if (ret == B_OK) 2921 ret = queryPath->Append(kQueriesDirectory); 2922 2923 return ret; 2924 } 2925 2926 2927 void 2928 TMailWindow::_RebuildQueryMenu(bool firstTime) 2929 { 2930 while (fQueryMenu->ItemAt(0)) { 2931 BMenuItem* item = fQueryMenu->RemoveItem((int32)0); 2932 delete item; 2933 } 2934 2935 fQueryMenu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE("Edit Queries" B_UTF8_ELLIPSIS,"???" B_UTF8_ELLIPSIS), 2936 new BMessage(M_EDIT_QUERIES), 'E', B_SHIFT_KEY)); 2937 2938 bool queryItemsAdded = false; 2939 2940 BPath queryPath; 2941 if (_GetQueryPath(&queryPath) < B_OK) 2942 return; 2943 2944 BDirectory queryDir(queryPath.Path()); 2945 2946 if (firstTime) { 2947 #ifdef __HAIKU__ 2948 BPrivate::BPathMonitor::StartWatching(queryPath.Path(), 2949 B_WATCH_RECURSIVELY, BMessenger(this, this)); 2950 #endif 2951 } 2952 2953 // If we find the named query, add it to the menu. 2954 BEntry entry; 2955 while (queryDir.GetNextEntry(&entry) == B_OK) { 2956 char name[B_FILE_NAME_LENGTH + 1]; 2957 entry.GetName(name); 2958 2959 char* queryString = _BuildQueryString(&entry); 2960 if (queryString == NULL) 2961 continue; 2962 2963 queryItemsAdded = true; 2964 2965 QueryMenu* queryMenu = new QueryMenu(name, false); 2966 queryMenu->SetTargetForItems(be_app); 2967 queryMenu->SetPredicate(queryString); 2968 fQueryMenu->AddItem(queryMenu); 2969 2970 free(queryString); 2971 } 2972 2973 if (queryItemsAdded) 2974 fQueryMenu->AddItem(new BSeparatorItem(), 1); 2975 } 2976 2977 2978 char* 2979 TMailWindow::_BuildQueryString(BEntry* entry) const 2980 { 2981 BNode node(entry); 2982 if (node.InitCheck() != B_OK) 2983 return NULL; 2984 2985 uint32 mode; 2986 if (node.ReadAttr(kAttrQueryInitialMode, B_INT32_TYPE, 0, (int32*)&mode, 2987 sizeof(int32)) <= 0) { 2988 mode = kByNameItem; 2989 } 2990 2991 BString queryString; 2992 switch (mode) { 2993 case kByForumlaItem: 2994 { 2995 BString buffer; 2996 if (node.ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK) 2997 queryString << buffer; 2998 break; 2999 } 3000 3001 case kByNameItem: 3002 { 3003 BString buffer; 3004 if (node.ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK) 3005 queryString << "(name==*" << buffer << "*)"; 3006 break; 3007 } 3008 3009 case kByAttributeItem: 3010 { 3011 int32 count = 1; 3012 if (node.ReadAttr(kAttrQueryInitialNumAttrs, B_INT32_TYPE, 0, 3013 (int32 *)&mode, sizeof(int32)) <= 0) { 3014 count = 1; 3015 } 3016 3017 attr_info info; 3018 if (node.GetAttrInfo(kAttrQueryInitialAttrs, &info) != B_OK) 3019 break; 3020 3021 if (count > 1 ) 3022 queryString << "("; 3023 3024 char *buffer = new char[info.size]; 3025 if (node.ReadAttr(kAttrQueryInitialAttrs, B_MESSAGE_TYPE, 0, 3026 buffer, (size_t)info.size) == info.size) { 3027 BMessage message; 3028 if (message.Unflatten(buffer) == B_OK) { 3029 for (int32 index = 0; /*index < count*/; index++) { 3030 const char *field; 3031 const char *value; 3032 if (message.FindString("menuSelection", index, &field) 3033 != B_OK 3034 || message.FindString("attrViewText", index, &value) 3035 != B_OK) { 3036 break; 3037 } 3038 3039 // ignore the mime type, we'll force it to be email 3040 // later 3041 if (strcmp(field, "BEOS:TYPE") != 0) { 3042 // TODO: check if subMenu contains the type of 3043 // comparison we are suppose to make here 3044 queryString << "(" << field << "==\"" 3045 << value << "\")"; 3046 3047 int32 logicMenuSelectedIndex; 3048 if (message.FindInt32("logicalRelation", index, 3049 &logicMenuSelectedIndex) == B_OK) { 3050 if (logicMenuSelectedIndex == 0) 3051 queryString << "&&"; 3052 else if (logicMenuSelectedIndex == 1) 3053 queryString << "||"; 3054 } else 3055 break; 3056 } 3057 } 3058 } 3059 } 3060 3061 if (count > 1 ) 3062 queryString << ")"; 3063 3064 delete [] buffer; 3065 break; 3066 } 3067 3068 default: 3069 break; 3070 } 3071 3072 if (queryString.Length() == 0) 3073 return NULL; 3074 3075 // force it to check for email only 3076 if (queryString.FindFirst("text/x-email") < 0 ) { 3077 BString temp; 3078 temp << "(" << queryString << "&&(BEOS:TYPE==\"text/x-email\"))"; 3079 queryString = temp; 3080 } 3081 3082 return strdup(queryString.String()); 3083 } 3084 3085