1 /* 2 Open Tracker License 3 4 Terms and Conditions 5 6 Copyright (c) 1991-2001, Be Incorporated. All rights reserved. 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy of 9 this software and associated documentation files (the "Software"), to deal in 10 the Software without restriction, including without limitation the rights to 11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 of the Software, and to permit persons to whom the Software is furnished to do 13 so, subject to the following conditions: 14 15 The above copyright notice and this permission notice applies to all licensees 16 and shall be included in all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN 23 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 25 Except as contained in this notice, the name of Be Incorporated shall not be 26 used in advertising or otherwise to promote the sale, use or other dealings in 27 this Software without prior written authorization from Be Incorporated. 28 29 BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or 30 registered trademarks of Be Incorporated in the United States and other 31 countries. Other brand product names are registered trademarks or trademarks 32 of their respective holders. All rights reserved. 33 */ 34 35 36 #include "Signature.h" 37 38 #include <stdio.h> 39 #include <stdlib.h> 40 #include <strings.h> 41 42 #include <Clipboard.h> 43 #include <Directory.h> 44 #include <LayoutBuilder.h> 45 #include <Locale.h> 46 #include <ScrollView.h> 47 #include <StringView.h> 48 49 #include "MailApp.h" 50 #include "MailPopUpMenu.h" 51 #include "MailSupport.h" 52 #include "MailWindow.h" 53 #include "Messages.h" 54 55 56 #define B_TRANSLATION_CONTEXT "Mail" 57 58 59 const float kSigHeight = 250; 60 const float kSigWidth = 300; 61 62 extern const char* kUndoStrings[]; 63 extern const char* kRedoStrings[]; 64 65 66 TSignatureWindow::TSignatureWindow(BRect rect) 67 : 68 BWindow(rect, B_TRANSLATE("Signatures"), B_TITLED_WINDOW, 69 B_AUTO_UPDATE_SIZE_LIMITS), 70 fFile(NULL) 71 { 72 BMenuItem* item; 73 74 // Set up the menu 75 BMenuBar* menuBar = new BMenuBar("MenuBar"); 76 BMenu* menu = new BMenu(B_TRANSLATE("Signature")); 77 menu->AddItem(fNew = new BMenuItem(B_TRANSLATE("New"), 78 new BMessage(M_NEW), 'N')); 79 fSignature = new TMenu(B_TRANSLATE("Open"), INDEX_SIGNATURE, M_SIGNATURE); 80 menu->AddItem(new BMenuItem(fSignature)); 81 menu->AddSeparatorItem(); 82 menu->AddItem(fSave = new BMenuItem(B_TRANSLATE("Save"), 83 new BMessage(M_SAVE), 'S')); 84 menu->AddItem(fDelete = new BMenuItem(B_TRANSLATE("Delete"), 85 new BMessage(M_DELETE), 'T')); 86 menuBar->AddItem(menu); 87 88 menu = new BMenu(B_TRANSLATE("Edit")); 89 menu->AddItem(fUndo = new BMenuItem(B_TRANSLATE("Undo"), 90 new BMessage(B_UNDO), 'Z')); 91 fUndo->SetTarget(NULL, this); 92 menu->AddSeparatorItem(); 93 menu->AddItem(fCut = new BMenuItem(B_TRANSLATE("Cut"), 94 new BMessage(B_CUT), 'X')); 95 fCut->SetTarget(NULL, this); 96 menu->AddItem(fCopy = new BMenuItem(B_TRANSLATE("Copy"), 97 new BMessage(B_COPY), 'C')); 98 fCopy->SetTarget(NULL, this); 99 menu->AddItem(fPaste = new BMenuItem(B_TRANSLATE("Paste"), 100 new BMessage(B_PASTE), 'V')); 101 fPaste->SetTarget(NULL, this); 102 menu->AddSeparatorItem(); 103 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Select all"), 104 new BMessage(M_SELECT), 'A')); 105 item->SetTarget(NULL, this); 106 menuBar->AddItem(menu); 107 108 fSigView = new TSignatureView(); 109 110 BLayoutBuilder::Group<>(this, B_VERTICAL, 0) 111 .Add(menuBar) 112 .Add(fSigView); 113 114 if (!rect.IsValid()) { 115 float fontFactor = be_plain_font->Size() / 12.0f; 116 ResizeTo(kSigWidth * fontFactor, kSigHeight * fontFactor); 117 // TODO: this should work, too, but doesn't 118 //ResizeToPreferred(); 119 } 120 } 121 122 123 TSignatureWindow::~TSignatureWindow() 124 { 125 } 126 127 128 void 129 TSignatureWindow::MenusBeginning() 130 { 131 fDelete->SetEnabled(fFile); 132 fSave->SetEnabled(IsDirty()); 133 fUndo->SetEnabled(false); // ***TODO*** 134 135 BTextView* textView = fSigView->fName->TextView(); 136 int32 finish = 0; 137 int32 start = 0; 138 if (textView->IsFocus()) 139 textView->GetSelection(&start, &finish); 140 else 141 fSigView->fTextView->GetSelection(&start, &finish); 142 143 fCut->SetEnabled(start != finish); 144 fCopy->SetEnabled(start != finish); 145 146 fNew->SetEnabled(textView->TextLength() 147 | fSigView->fTextView->TextLength()); 148 be_clipboard->Lock(); 149 fPaste->SetEnabled(be_clipboard->Data()->HasData("text/plain", 150 B_MIME_TYPE)); 151 be_clipboard->Unlock(); 152 153 // Undo stuff 154 bool isRedo = false; 155 undo_state undoState = B_UNDO_UNAVAILABLE; 156 157 BTextView *focusTextView = dynamic_cast<BTextView *>(CurrentFocus()); 158 if (focusTextView != NULL) 159 undoState = focusTextView->UndoState(&isRedo); 160 161 fUndo->SetLabel(isRedo ? kRedoStrings[undoState] : kUndoStrings[undoState]); 162 fUndo->SetEnabled(undoState != B_UNDO_UNAVAILABLE); 163 } 164 165 166 void 167 TSignatureWindow::MessageReceived(BMessage* msg) 168 { 169 switch (msg->what) { 170 case CHANGE_FONT: 171 { 172 BFont* font; 173 msg->FindPointer("font", (void **)&font); 174 fSigView->fTextView->SetFontAndColor(font); 175 fSigView->fTextView->Invalidate(fSigView->fTextView->Bounds()); 176 break; 177 } 178 179 case M_NEW: 180 if (Clear()) { 181 fSigView->fName->SetText(""); 182 fSigView->fTextView->SetText(""); 183 fSigView->fName->MakeFocus(true); 184 } 185 break; 186 187 case M_SAVE: 188 Save(); 189 break; 190 191 case M_DELETE: { 192 BAlert* alert = new BAlert("", 193 B_TRANSLATE("Really delete this signature? This cannot " 194 "be undone."), 195 B_TRANSLATE("Cancel"), 196 B_TRANSLATE("Delete"), 197 NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 198 alert->SetShortcut(0, B_ESCAPE); 199 int32 choice = alert->Go(); 200 201 if (choice == 0) 202 break; 203 204 if (fFile) { 205 delete fFile; 206 fFile = NULL; 207 fEntry.Remove(); 208 fSigView->fName->SetText(""); 209 fSigView->fTextView->SetText(NULL, (int32)0); 210 fSigView->fName->MakeFocus(true); 211 } 212 break; 213 } 214 case M_SIGNATURE: 215 if (Clear()) { 216 entry_ref ref; 217 msg->FindRef("ref", &ref); 218 fEntry.SetTo(&ref); 219 fFile = new BFile(&ref, O_RDWR); 220 if (fFile->InitCheck() == B_OK) { 221 char name[B_FILE_NAME_LENGTH]; 222 fFile->ReadAttr(INDEX_SIGNATURE, B_STRING_TYPE, 0, name, 223 sizeof(name)); 224 fSigView->fName->SetText(name); 225 226 off_t size; 227 fFile->GetSize(&size); 228 char* sig = (char*)malloc(size); 229 if (sig == NULL) 230 break; 231 232 size = fFile->Read(sig, size); 233 fSigView->fTextView->SetText(sig, size); 234 fSigView->fName->MakeFocus(true); 235 BTextView* textView = fSigView->fName->TextView(); 236 textView->Select(0, textView->TextLength()); 237 fSigView->fTextView->fDirty = false; 238 } else { 239 fFile = NULL; 240 beep(); 241 BAlert* alert = new BAlert("", 242 B_TRANSLATE("Couldn't open this signature. Sorry."), 243 B_TRANSLATE("OK")); 244 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 245 alert->Go(); 246 } 247 } 248 break; 249 250 default: 251 BWindow::MessageReceived(msg); 252 break; 253 } 254 } 255 256 257 bool 258 TSignatureWindow::QuitRequested() 259 { 260 if (Clear()) { 261 BMessage msg(WINDOW_CLOSED); 262 msg.AddInt32("kind", SIG_WINDOW); 263 msg.AddRect("window frame", Frame()); 264 265 be_app->PostMessage(&msg); 266 return true; 267 } 268 return false; 269 } 270 271 272 void 273 TSignatureWindow::FrameResized(float width, float height) 274 { 275 fSigView->FrameResized(width, height); 276 } 277 278 279 void 280 TSignatureWindow::Show() 281 { 282 Lock(); 283 BTextView* textView = (BTextView *)fSigView->fName->TextView(); 284 fSigView->fName->MakeFocus(true); 285 textView->Select(0, textView->TextLength()); 286 Unlock(); 287 288 BWindow::Show(); 289 } 290 291 292 bool 293 TSignatureWindow::Clear() 294 { 295 if (IsDirty()) { 296 beep(); 297 BAlert *alert = new BAlert("", 298 B_TRANSLATE("Save changes to this signature?"), 299 B_TRANSLATE("Cancel"), 300 B_TRANSLATE("Don't save"), 301 B_TRANSLATE("Save"), 302 B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT); 303 alert->SetShortcut(0, B_ESCAPE); 304 alert->SetShortcut(1, 'd'); 305 alert->SetShortcut(2, 's'); 306 int32 result = alert->Go(); 307 if (result == 0) 308 return false; 309 if (result == 2) 310 Save(); 311 } 312 313 delete fFile; 314 fFile = NULL; 315 fSigView->fTextView->fDirty = false; 316 return true; 317 } 318 319 320 bool 321 TSignatureWindow::IsDirty() 322 { 323 if (fFile != NULL) { 324 char name[B_FILE_NAME_LENGTH]; 325 fFile->ReadAttr(INDEX_SIGNATURE, B_STRING_TYPE, 0, name, sizeof(name)); 326 if (strcmp(name, fSigView->fName->Text()) != 0 327 || fSigView->fTextView->fDirty) { 328 return true; 329 } 330 } else if (fSigView->fName->Text()[0] != '\0' 331 || fSigView->fTextView->TextLength() != 0) { 332 return true; 333 } 334 return false; 335 } 336 337 338 void 339 TSignatureWindow::Save() 340 { 341 char name[B_FILE_NAME_LENGTH]; 342 int32 index = 0; 343 status_t result; 344 BDirectory dir; 345 BEntry entry; 346 BNodeInfo *node; 347 BPath path; 348 349 if (!fFile) { 350 find_directory(B_USER_SETTINGS_DIRECTORY, &path, true); 351 dir.SetTo(path.Path()); 352 353 if (dir.FindEntry("Mail", &entry) == B_NO_ERROR) 354 dir.SetTo(&entry); 355 else 356 dir.CreateDirectory("Mail", &dir); 357 358 if (dir.InitCheck() != B_NO_ERROR) 359 goto err_exit; 360 361 if (dir.FindEntry("signatures", &entry) == B_NO_ERROR) 362 dir.SetTo(&entry); 363 else 364 dir.CreateDirectory("signatures", &dir); 365 366 if (dir.InitCheck() != B_NO_ERROR) 367 goto err_exit; 368 369 fFile = new BFile(); 370 while(true) { 371 sprintf(name, "signature_%" B_PRId32, index++); 372 if ((result = dir.CreateFile(name, fFile, true)) == B_NO_ERROR) 373 break; 374 if (result != EEXIST) 375 goto err_exit; 376 } 377 dir.FindEntry(name, &fEntry); 378 node = new BNodeInfo(fFile); 379 node->SetType("text/plain"); 380 delete node; 381 } 382 383 fSigView->fTextView->fDirty = false; 384 fFile->Seek(0, 0); 385 fFile->Write(fSigView->fTextView->Text(), 386 fSigView->fTextView->TextLength()); 387 fFile->SetSize(fFile->Position()); 388 fFile->WriteAttr(INDEX_SIGNATURE, B_STRING_TYPE, 0, fSigView->fName->Text(), 389 strlen(fSigView->fName->Text()) + 1); 390 return; 391 392 err_exit: 393 beep(); 394 BAlert* alert = new BAlert("", 395 B_TRANSLATE("An error occurred trying to save this signature."), 396 B_TRANSLATE("Sorry")); 397 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 398 alert->Go(); 399 } 400 401 402 // #pragma mark - 403 404 405 TSignatureView::TSignatureView() 406 : 407 BGridView("SigView") 408 { 409 GridLayout()->SetInsets(B_USE_DEFAULT_SPACING); 410 411 BStringView* nameLabel = new BStringView("NameLabel", 412 B_TRANSLATE("Title:")); 413 nameLabel->SetAlignment(B_ALIGN_RIGHT); 414 GridLayout()->AddView(nameLabel, 0, 0); 415 416 fName = new TNameControl("", new BMessage(NAME_FIELD)); 417 GridLayout()->AddItem(fName->CreateTextViewLayoutItem(), 1, 0); 418 419 BStringView* signatureLabel = new BStringView("SigLabel", 420 B_TRANSLATE("Signature:")); 421 signatureLabel->SetAlignment(B_ALIGN_RIGHT); 422 GridLayout()->AddView(signatureLabel, 0, 1); 423 424 fTextView = new TSigTextView(); 425 426 font_height fontHeight; 427 fTextView->GetFontHeight(&fontHeight); 428 float lineHeight = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent); 429 430 BScrollView* scroller = new BScrollView("SigScroller", fTextView, 0, 431 false, true); 432 scroller->SetExplicitPreferredSize( 433 BSize(fTextView->StringWidth("W") * 30, lineHeight * 6)); 434 435 GridLayout()->AddView(scroller, 1, 1, 1, 2); 436 437 GridLayout()->AddItem(BSpaceLayoutItem::CreateGlue(), 0, 2); 438 } 439 440 441 void 442 TSignatureView::AttachedToWindow() 443 { 444 } 445 446 447 // #pragma mark - 448 449 450 TNameControl::TNameControl(const char* label, BMessage* invocationMessage) 451 : 452 BTextControl("", label, "", invocationMessage) 453 { 454 strcpy(fLabel, label); 455 } 456 457 458 void 459 TNameControl::AttachedToWindow() 460 { 461 BTextControl::AttachedToWindow(); 462 463 TextView()->SetMaxBytes(B_FILE_NAME_LENGTH - 1); 464 } 465 466 467 void 468 TNameControl::MessageReceived(BMessage* msg) 469 { 470 switch (msg->what) { 471 case M_SELECT: 472 TextView()->Select(0, TextView()->TextLength()); 473 break; 474 475 default: 476 BTextControl::MessageReceived(msg); 477 } 478 } 479 480 481 // #pragma mark - 482 483 484 TSigTextView::TSigTextView() 485 : 486 BTextView("SignatureView", B_NAVIGABLE | B_WILL_DRAW) 487 { 488 fDirty = false; 489 SetDoesUndo(true); 490 } 491 492 493 void 494 TSigTextView::DeleteText(int32 offset, int32 len) 495 { 496 fDirty = true; 497 BTextView::DeleteText(offset, len); 498 } 499 500 501 void 502 TSigTextView::InsertText(const char *text, int32 len, int32 offset, 503 const text_run_array *runs) 504 { 505 fDirty = true; 506 BTextView::InsertText(text, len, offset, runs); 507 } 508 509 510 void 511 TSigTextView::KeyDown(const char *key, int32 count) 512 { 513 bool up = false; 514 int32 height; 515 BRect r; 516 517 switch (key[0]) { 518 case B_HOME: 519 Select(0, 0); 520 ScrollToSelection(); 521 break; 522 523 case B_END: 524 Select(TextLength(), TextLength()); 525 ScrollToSelection(); 526 break; 527 528 case B_PAGE_UP: 529 up = true; 530 case B_PAGE_DOWN: 531 r = Bounds(); 532 height = (int32)((up ? r.top - r.bottom : r.bottom - r.top) - 25); 533 if ((up) && (!r.top)) 534 break; 535 ScrollBy(0, height); 536 break; 537 538 default: 539 BTextView::KeyDown(key, count); 540 break; 541 } 542 } 543 544 545 void 546 TSigTextView::MessageReceived(BMessage *msg) 547 { 548 char type[B_FILE_NAME_LENGTH]; 549 char *text; 550 int32 end; 551 int32 start; 552 BFile file; 553 BNodeInfo *node; 554 entry_ref ref; 555 off_t size; 556 557 switch (msg->what) { 558 case B_SIMPLE_DATA: 559 if (msg->HasRef("refs")) { 560 msg->FindRef("refs", &ref); 561 file.SetTo(&ref, O_RDONLY); 562 if (file.InitCheck() == B_NO_ERROR) { 563 node = new BNodeInfo(&file); 564 node->GetType(type); 565 delete node; 566 file.GetSize(&size); 567 if ((!strncasecmp(type, "text/", 5)) && (size)) { 568 text = (char *)malloc(size); 569 file.Read(text, size); 570 Delete(); 571 GetSelection(&start, &end); 572 Insert(text, size); 573 Select(start, start + size); 574 free(text); 575 } 576 } 577 } 578 else 579 BTextView::MessageReceived(msg); 580 break; 581 582 case M_SELECT: 583 if (IsSelectable()) 584 Select(0, TextLength()); 585 break; 586 587 default: 588 BTextView::MessageReceived(msg); 589 break; 590 } 591 } 592