1 /* 2 * Copyright 2002-2012, Haiku, Inc. All Rights Reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Mattias Sundblad 7 * Andrew Bachmann 8 * Philippe Saint-Pierre 9 * Jonas Sundström 10 * Ryan Leavengood 11 * Vlad Slepukhin 12 * Sarzhuk Zharski 13 */ 14 15 16 #include "ColorMenuItem.h" 17 #include "Constants.h" 18 #include "FindWindow.h" 19 #include "ReplaceWindow.h" 20 #include "StatusView.h" 21 #include "StyledEditApp.h" 22 #include "StyledEditView.h" 23 #include "StyledEditWindow.h" 24 25 #include <Alert.h> 26 #include <Autolock.h> 27 #include <Catalog.h> 28 #include <CharacterSet.h> 29 #include <CharacterSetRoster.h> 30 #include <Clipboard.h> 31 #include <Debug.h> 32 #include <File.h> 33 #include <FilePanel.h> 34 #include <fs_attr.h> 35 #include <Locale.h> 36 #include <Menu.h> 37 #include <MenuBar.h> 38 #include <MenuItem.h> 39 #include <NodeMonitor.h> 40 #include <Path.h> 41 #include <PrintJob.h> 42 #include <Rect.h> 43 #include <Roster.h> 44 #include <Screen.h> 45 #include <ScrollView.h> 46 #include <TextControl.h> 47 #include <TextView.h> 48 #include <TranslationUtils.h> 49 #include <UnicodeChar.h> 50 51 52 using namespace BPrivate; 53 54 55 const float kLineViewWidth = 30.0; 56 const char* kInfoAttributeName = "StyledEdit-info"; 57 58 59 #undef B_TRANSLATION_CONTEXT 60 #define B_TRANSLATION_CONTEXT "StyledEditWindow" 61 62 63 // This is a temporary solution for building BString with printf like format. 64 // will be removed in the future. 65 static void 66 bs_printf(BString* string, const char* format, ...) 67 { 68 va_list ap; 69 va_start(ap, format); 70 char* buf; 71 vasprintf(&buf, format, ap); 72 string->SetTo(buf); 73 free(buf); 74 va_end(ap); 75 } 76 77 78 // #pragma mark - 79 80 81 StyledEditWindow::StyledEditWindow(BRect frame, int32 id, uint32 encoding) 82 : BWindow(frame, "untitled", B_DOCUMENT_WINDOW, B_ASYNCHRONOUS_CONTROLS) 83 { 84 _InitWindow(encoding); 85 BString unTitled(B_TRANSLATE("Untitled ")); 86 unTitled << id; 87 SetTitle(unTitled.String()); 88 fSaveItem->SetEnabled(true); 89 // allow saving empty files 90 Show(); 91 } 92 93 94 StyledEditWindow::StyledEditWindow(BRect frame, entry_ref* ref, uint32 encoding) 95 : BWindow(frame, "untitled", B_DOCUMENT_WINDOW, B_ASYNCHRONOUS_CONTROLS) 96 { 97 _InitWindow(encoding); 98 OpenFile(ref); 99 Show(); 100 } 101 102 103 StyledEditWindow::~StyledEditWindow() 104 { 105 delete fSaveMessage; 106 delete fPrintSettings; 107 delete fSavePanel; 108 } 109 110 111 void 112 StyledEditWindow::Quit() 113 { 114 _SwitchNodeMonitor(false); 115 116 _SaveAttrs(); 117 if (StyledEditApp* app = dynamic_cast<StyledEditApp*>(be_app)) 118 app->CloseDocument(); 119 BWindow::Quit(); 120 } 121 122 123 #undef B_TRANSLATION_CONTEXT 124 #define B_TRANSLATION_CONTEXT "QuitAlert" 125 126 127 bool 128 StyledEditWindow::QuitRequested() 129 { 130 if (fClean) 131 return true; 132 133 if (fTextView->TextLength() == 0 && fSaveMessage == NULL) 134 return true; 135 136 BString alertText; 137 bs_printf(&alertText, 138 B_TRANSLATE("Save changes to the document \"%s\"? "), Title()); 139 140 int32 index = _ShowAlert(alertText, B_TRANSLATE("Cancel"), 141 B_TRANSLATE("Don't save"), B_TRANSLATE("Save"), B_WARNING_ALERT); 142 143 if (index == 0) 144 return false; // "cancel": dont save, dont close the window 145 146 if (index == 1) 147 return true; // "don't save": just close the window 148 149 if (!fSaveMessage) { 150 SaveAs(new BMessage(SAVE_THEN_QUIT)); 151 return false; 152 } 153 154 return Save() == B_OK; 155 } 156 157 158 void 159 StyledEditWindow::MessageReceived(BMessage* message) 160 { 161 if (message->WasDropped()) { 162 entry_ref ref; 163 if (message->FindRef("refs", 0, &ref)==B_OK) { 164 message->what = B_REFS_RECEIVED; 165 be_app->PostMessage(message); 166 } 167 } 168 169 switch (message->what) { 170 // File menu 171 case MENU_SAVE: 172 if (!fSaveMessage) 173 SaveAs(); 174 else 175 Save(fSaveMessage); 176 break; 177 178 case MENU_SAVEAS: 179 SaveAs(); 180 break; 181 182 case B_SAVE_REQUESTED: 183 Save(message); 184 break; 185 186 case SAVE_THEN_QUIT: 187 if (Save(message) == B_OK) 188 Quit(); 189 break; 190 191 case MENU_RELOAD: 192 _ReloadDocument(message); 193 break; 194 195 case MENU_CLOSE: 196 if (QuitRequested()) 197 Quit(); 198 break; 199 200 case MENU_PAGESETUP: 201 PageSetup(fTextView->Window()->Title()); 202 break; 203 case MENU_PRINT: 204 Print(fTextView->Window()->Title()); 205 break; 206 case MENU_QUIT: 207 be_app->PostMessage(B_QUIT_REQUESTED); 208 break; 209 210 // Edit menu 211 212 case B_UNDO: 213 ASSERT(fCanUndo || fCanRedo); 214 ASSERT(!(fCanUndo && fCanRedo)); 215 if (fCanUndo) 216 fUndoFlag = true; 217 if (fCanRedo) 218 fRedoFlag = true; 219 220 fTextView->Undo(be_clipboard); 221 break; 222 case B_CUT: 223 fTextView->Cut(be_clipboard); 224 break; 225 case B_COPY: 226 fTextView->Copy(be_clipboard); 227 break; 228 case B_PASTE: 229 fTextView->Paste(be_clipboard); 230 break; 231 case MENU_CLEAR: 232 fTextView->Clear(); 233 break; 234 case MENU_FIND: 235 { 236 BRect findWindowFrame(100, 100, 400, 235); 237 BWindow* window = new FindWindow(findWindowFrame, this, 238 &fStringToFind, fCaseSensitive, fWrapAround, fBackSearch); 239 window->Show(); 240 break; 241 } 242 case MSG_SEARCH: 243 message->FindString("findtext", &fStringToFind); 244 fFindAgainItem->SetEnabled(true); 245 message->FindBool("casesens", &fCaseSensitive); 246 message->FindBool("wrap", &fWrapAround); 247 message->FindBool("backsearch", &fBackSearch); 248 249 _Search(fStringToFind, fCaseSensitive, fWrapAround, fBackSearch); 250 break; 251 case MENU_FIND_AGAIN: 252 _Search(fStringToFind, fCaseSensitive, fWrapAround, fBackSearch); 253 break; 254 case MENU_FIND_SELECTION: 255 _FindSelection(); 256 break; 257 case MENU_REPLACE: 258 { 259 BRect replaceWindowFrame(100, 100, 400, 284); 260 BWindow* window = new ReplaceWindow(replaceWindowFrame, this, 261 &fStringToFind, &fReplaceString, fCaseSensitive, fWrapAround, 262 fBackSearch); 263 window->Show(); 264 break; 265 } 266 case MSG_REPLACE: 267 { 268 message->FindBool("casesens", &fCaseSensitive); 269 message->FindBool("wrap", &fWrapAround); 270 message->FindBool("backsearch", &fBackSearch); 271 272 message->FindString("FindText", &fStringToFind); 273 message->FindString("ReplaceText", &fReplaceString); 274 275 fFindAgainItem->SetEnabled(true); 276 fReplaceSameItem->SetEnabled(true); 277 278 _Replace(fStringToFind, fReplaceString, fCaseSensitive, fWrapAround, 279 fBackSearch); 280 break; 281 } 282 case MENU_REPLACE_SAME: 283 _Replace(fStringToFind, fReplaceString, fCaseSensitive, fWrapAround, 284 fBackSearch); 285 break; 286 287 case MSG_REPLACE_ALL: 288 { 289 message->FindBool("casesens", &fCaseSensitive); 290 message->FindString("FindText",&fStringToFind); 291 message->FindString("ReplaceText",&fReplaceString); 292 293 bool allWindows; 294 message->FindBool("allwindows", &allWindows); 295 296 fFindAgainItem->SetEnabled(true); 297 fReplaceSameItem->SetEnabled(true); 298 299 if (allWindows) 300 SearchAllWindows(fStringToFind, fReplaceString, fCaseSensitive); 301 else 302 _ReplaceAll(fStringToFind, fReplaceString, fCaseSensitive); 303 break; 304 } 305 306 case B_NODE_MONITOR: 307 _HandleNodeMonitorEvent(message); 308 break; 309 310 // Font menu 311 312 case FONT_SIZE: 313 { 314 float fontSize; 315 if (message->FindFloat("size", &fontSize) == B_OK) 316 _SetFontSize(fontSize); 317 break; 318 } 319 case FONT_FAMILY: 320 { 321 const char* fontFamily = NULL; 322 const char* fontStyle = NULL; 323 void* ptr; 324 if (message->FindPointer("source", &ptr) == B_OK) { 325 BMenuItem* item = static_cast<BMenuItem*>(ptr); 326 fontFamily = item->Label(); 327 } 328 329 BFont font; 330 font.SetFamilyAndStyle(fontFamily, fontStyle); 331 fItalicItem->SetMarked((font.Face() & B_ITALIC_FACE) != 0); 332 fBoldItem->SetMarked((font.Face() & B_BOLD_FACE) != 0); 333 334 _SetFontStyle(fontFamily, fontStyle); 335 break; 336 } 337 case FONT_STYLE: 338 { 339 const char* fontFamily = NULL; 340 const char* fontStyle = NULL; 341 void* ptr; 342 if (message->FindPointer("source", &ptr) == B_OK) { 343 BMenuItem* item = static_cast<BMenuItem*>(ptr); 344 fontStyle = item->Label(); 345 BMenu* menu = item->Menu(); 346 if (menu != NULL) { 347 BMenuItem* super_item = menu->Superitem(); 348 if (super_item != NULL) 349 fontFamily = super_item->Label(); 350 } 351 } 352 353 BFont font; 354 font.SetFamilyAndStyle(fontFamily, fontStyle); 355 fItalicItem->SetMarked((font.Face() & B_ITALIC_FACE) != 0); 356 fBoldItem->SetMarked((font.Face() & B_BOLD_FACE) != 0); 357 358 _SetFontStyle(fontFamily, fontStyle); 359 break; 360 } 361 case kMsgSetItalic: 362 { 363 uint32 sameProperties; 364 BFont font; 365 fTextView->GetFontAndColor(&font, &sameProperties); 366 367 if (fItalicItem->IsMarked()) 368 font.SetFace(B_REGULAR_FACE); 369 fItalicItem->SetMarked(!fItalicItem->IsMarked()); 370 371 font_family family; 372 font_style style; 373 font.GetFamilyAndStyle(&family, &style); 374 375 _SetFontStyle(family, style); 376 break; 377 } 378 case kMsgSetBold: 379 { 380 uint32 sameProperties; 381 BFont font; 382 fTextView->GetFontAndColor(&font, &sameProperties); 383 384 if (fBoldItem->IsMarked()) 385 font.SetFace(B_REGULAR_FACE); 386 fBoldItem->SetMarked(!fBoldItem->IsMarked()); 387 388 font_family family; 389 font_style style; 390 font.GetFamilyAndStyle(&family, &style); 391 392 _SetFontStyle(family, style); 393 break; 394 } 395 case FONT_COLOR: 396 { 397 void* ptr; 398 if (message->FindPointer("source", &ptr) == B_OK) { 399 if (ptr == fBlackItem) 400 _SetFontColor(&BLACK); 401 else if (ptr == fRedItem) 402 _SetFontColor(&RED); 403 else if (ptr == fGreenItem) 404 _SetFontColor(&GREEN); 405 else if (ptr == fBlueItem) 406 _SetFontColor(&BLUE); 407 else if (ptr == fCyanItem) 408 _SetFontColor(&CYAN); 409 else if (ptr == fMagentaItem) 410 _SetFontColor(&MAGENTA); 411 else if (ptr == fYellowItem) 412 _SetFontColor(&YELLOW); 413 } 414 break; 415 } 416 417 // Document menu 418 419 case ALIGN_LEFT: 420 fTextView->SetAlignment(B_ALIGN_LEFT); 421 _UpdateCleanUndoRedoSaveRevert(); 422 break; 423 case ALIGN_CENTER: 424 fTextView->SetAlignment(B_ALIGN_CENTER); 425 _UpdateCleanUndoRedoSaveRevert(); 426 break; 427 case ALIGN_RIGHT: 428 fTextView->SetAlignment(B_ALIGN_RIGHT); 429 _UpdateCleanUndoRedoSaveRevert(); 430 break; 431 case WRAP_LINES: 432 { 433 BRect textRect(fTextView->Bounds()); 434 textRect.OffsetTo(B_ORIGIN); 435 textRect.InsetBy(TEXT_INSET, TEXT_INSET); 436 if (fTextView->DoesWordWrap()) { 437 fTextView->SetWordWrap(false); 438 fWrapItem->SetMarked(false); 439 // the width comes from stylededit R5. TODO: find a better way 440 textRect.SetRightBottom(BPoint(1500.0, textRect.RightBottom().y)); 441 } else { 442 fTextView->SetWordWrap(true); 443 fWrapItem->SetMarked(true); 444 } 445 fTextView->SetTextRect(textRect); 446 447 _UpdateCleanUndoRedoSaveRevert(); 448 break; 449 } 450 case SHOW_STATISTICS: 451 _ShowStatistics(); 452 break; 453 case ENABLE_ITEMS: 454 fCutItem->SetEnabled(true); 455 fCopyItem->SetEnabled(true); 456 break; 457 case DISABLE_ITEMS: 458 fCutItem->SetEnabled(false); 459 fCopyItem->SetEnabled(false); 460 break; 461 case TEXT_CHANGED: 462 if (fUndoFlag) { 463 if (fUndoCleans) { 464 // we cleaned! 465 fClean = true; 466 fUndoCleans = false; 467 } else if (fClean) { 468 // if we were clean 469 // then a redo will make us clean again 470 fRedoCleans = true; 471 fClean = false; 472 } 473 // set mode 474 fCanUndo = false; 475 fCanRedo = true; 476 fUndoItem->SetLabel(B_TRANSLATE("Redo typing")); 477 fUndoItem->SetEnabled(true); 478 fUndoFlag = false; 479 } else { 480 if (fRedoFlag && fRedoCleans) { 481 // we cleaned! 482 fClean = true; 483 fRedoCleans = false; 484 } else if (fClean) { 485 // if we were clean 486 // then an undo will make us clean again 487 fUndoCleans = true; 488 fClean = false; 489 } else { 490 // no more cleaning from undo now... 491 fUndoCleans = false; 492 } 493 // set mode 494 fCanUndo = true; 495 fCanRedo = false; 496 fUndoItem->SetLabel(B_TRANSLATE("Undo typing")); 497 fUndoItem->SetEnabled(true); 498 fRedoFlag = false; 499 } 500 if (fClean) { 501 fSaveItem->SetEnabled(fSaveMessage == NULL); 502 } else { 503 fSaveItem->SetEnabled(true); 504 } 505 fReloadItem->SetEnabled(fSaveMessage != NULL); 506 fEncodingItem->SetEnabled(fSaveMessage != NULL); 507 break; 508 509 case SAVE_AS_ENCODING: 510 void* ptr; 511 if (message->FindPointer("source", &ptr) == B_OK 512 && fSavePanelEncodingMenu != NULL) { 513 fTextView->SetEncoding( 514 (uint32)fSavePanelEncodingMenu->IndexOf((BMenuItem*)ptr)); 515 } 516 break; 517 518 case UPDATE_STATUS: 519 message->AddBool("modified", !fClean); 520 message->AddBool("readOnly", !fTextView->IsEditable()); 521 fStatusView->SetStatus(message); 522 break; 523 524 case UNLOCK_FILE: 525 { 526 status_t status = _UnlockFile(); 527 if (status != B_OK) { 528 BString text; 529 bs_printf(&text, 530 B_TRANSLATE("Unable to unlock file\n\t%s"), 531 strerror(status)); 532 _ShowAlert(text, B_TRANSLATE("OK"), "", "", B_STOP_ALERT); 533 } 534 PostMessage(UPDATE_STATUS); 535 break; 536 } 537 538 default: 539 BWindow::MessageReceived(message); 540 break; 541 } 542 } 543 544 545 void 546 StyledEditWindow::MenusBeginning() 547 { 548 // set up the recent documents menu 549 BMessage documents; 550 be_roster->GetRecentDocuments(&documents, 9, NULL, APP_SIGNATURE); 551 552 // delete old items.. 553 // shatty: it would be preferable to keep the old 554 // menu around instead of continuously thrashing 555 // the menu, but unfortunately there does not 556 // seem to be a straightforward way to update it 557 // going backwards may simplify memory management 558 for (int i = fRecentMenu->CountItems(); i-- > 0;) { 559 delete fRecentMenu->RemoveItem(i); 560 } 561 562 // add new items 563 int count = 0; 564 entry_ref ref; 565 while (documents.FindRef("refs", count++, &ref) == B_OK) { 566 if (ref.device != -1 && ref.directory != -1) { 567 // sanity check passed 568 BMessage* openRecent = new BMessage(B_REFS_RECEIVED); 569 openRecent->AddRef("refs", &ref); 570 BMenuItem* item = new BMenuItem(ref.name, openRecent); 571 item->SetTarget(be_app); 572 fRecentMenu->AddItem(item); 573 } 574 } 575 576 // update the font menu 577 // unselect the old values 578 if (fCurrentFontItem != NULL) { 579 fCurrentFontItem->SetMarked(false); 580 BMenu* menu = fCurrentFontItem->Submenu(); 581 if (menu != NULL) { 582 BMenuItem* item = menu->FindMarked(); 583 if (item != NULL) { 584 item->SetMarked(false); 585 } 586 } 587 } 588 589 if (fCurrentStyleItem != NULL) { 590 fCurrentStyleItem->SetMarked(false); 591 } 592 593 BMenuItem* oldColorItem = fFontColorMenu->FindMarked(); 594 if (oldColorItem != NULL) 595 oldColorItem->SetMarked(false); 596 597 BMenuItem* oldSizeItem = fFontSizeMenu->FindMarked(); 598 if (oldSizeItem != NULL) 599 oldSizeItem->SetMarked(false); 600 601 // find the current font, color, size 602 BFont font; 603 uint32 sameProperties; 604 rgb_color color = BLACK; 605 bool sameColor; 606 fTextView->GetFontAndColor(&font, &sameProperties, &color, &sameColor); 607 608 if (sameColor && color.alpha == 255) { 609 // select the current color 610 if (color.red == 0) { 611 if (color.green == 0) { 612 if (color.blue == 0) { 613 fBlackItem->SetMarked(true); 614 } else if (color.blue == 255) { 615 fBlueItem->SetMarked(true); 616 } 617 } else if (color.green == 255) { 618 if (color.blue == 0) { 619 fGreenItem->SetMarked(true); 620 } else if (color.blue == 255) { 621 fCyanItem->SetMarked(true); 622 } 623 } 624 } else if (color.red == 255) { 625 if (color.green == 0) { 626 if (color.blue == 0) { 627 fRedItem->SetMarked(true); 628 } else if (color.blue == 255) { 629 fMagentaItem->SetMarked(true); 630 } 631 } else if (color.green == 255) { 632 if (color.blue == 0) { 633 fYellowItem->SetMarked(true); 634 } 635 } 636 } 637 } 638 639 if (sameProperties & B_FONT_SIZE) { 640 if ((int)font.Size() == font.Size()) { 641 // select the current font size 642 char fontSizeStr[16]; 643 snprintf(fontSizeStr, 15, "%i", (int)font.Size()); 644 BMenuItem* item = fFontSizeMenu->FindItem(fontSizeStr); 645 if (item != NULL) 646 item->SetMarked(true); 647 } 648 } 649 650 font_family family; 651 font_style style; 652 font.GetFamilyAndStyle(&family, &style); 653 654 fCurrentFontItem = fFontMenu->FindItem(family); 655 656 if (fCurrentFontItem != NULL) { 657 fCurrentFontItem->SetMarked(true); 658 BMenu* menu = fCurrentFontItem->Submenu(); 659 if (menu != NULL) { 660 BMenuItem* item = menu->FindItem(style); 661 fCurrentStyleItem = item; 662 if (fCurrentStyleItem != NULL) 663 item->SetMarked(true); 664 } 665 } 666 667 fBoldItem->SetMarked((font.Face() & B_BOLD_FACE) != 0); 668 fItalicItem->SetMarked((font.Face() & B_ITALIC_FACE) != 0); 669 670 switch (fTextView->Alignment()) { 671 case B_ALIGN_LEFT: 672 default: 673 fAlignLeft->SetMarked(true); 674 break; 675 case B_ALIGN_CENTER: 676 fAlignCenter->SetMarked(true); 677 break; 678 case B_ALIGN_RIGHT: 679 fAlignRight->SetMarked(true); 680 break; 681 } 682 683 // text encoding 684 const BCharacterSet* charset 685 = BCharacterSetRoster::GetCharacterSetByFontID(fTextView->GetEncoding()); 686 BMenu* encodingMenu = fEncodingItem->Submenu(); 687 if (charset != NULL && encodingMenu != NULL) { 688 const char* mime = charset->GetMIMEName(); 689 BString name(charset->GetPrintName()); 690 if (mime) 691 name << " (" << mime << ")"; 692 693 BMenuItem* item = encodingMenu->FindItem(name); 694 if (item != NULL) 695 item->SetMarked(true); 696 } 697 } 698 699 700 #undef B_TRANSLATION_CONTEXT 701 #define B_TRANSLATION_CONTEXT "SaveAlert" 702 703 704 status_t 705 StyledEditWindow::Save(BMessage* message) 706 { 707 _NodeMonitorSuspender nodeMonitorSuspender(this); 708 709 if (!message) 710 message = fSaveMessage; 711 712 if (!message) 713 return B_ERROR; 714 715 entry_ref dirRef; 716 const char* name; 717 if (message->FindRef("directory", &dirRef) != B_OK 718 || message->FindString("name", &name) != B_OK) 719 return B_BAD_VALUE; 720 721 BDirectory dir(&dirRef); 722 BEntry entry(&dir, name); 723 724 status_t status = B_ERROR; 725 if (dir.InitCheck() == B_OK && entry.InitCheck() == B_OK) { 726 struct stat st; 727 BFile file(&entry, B_READ_WRITE | B_CREATE_FILE); 728 if (file.InitCheck() == B_OK 729 && (status = file.GetStat(&st)) == B_OK) { 730 // check the file permissions 731 if (!((getuid() == st.st_uid && (S_IWUSR & st.st_mode)) 732 || (getgid() == st.st_gid && (S_IWGRP & st.st_mode)) 733 || (S_IWOTH & st.st_mode))) { 734 BString alertText; 735 bs_printf(&alertText, B_TRANSLATE("This file is marked " 736 "Read-Only. Save changes to the document \"%s\"? "), name); 737 switch (_ShowAlert(alertText, B_TRANSLATE("Cancel"), 738 B_TRANSLATE("Don't save"), 739 B_TRANSLATE("Save"), B_WARNING_ALERT)) { 740 case 0: 741 return B_CANCELED; 742 case 1: 743 return B_OK; 744 default: 745 break; 746 } 747 } 748 749 status = fTextView->WriteStyledEditFile(&file); 750 } 751 } 752 753 if (status != B_OK) { 754 BString alertText; 755 bs_printf(&alertText, B_TRANSLATE("Error saving \"%s\":\n%s"), name, 756 strerror(status)); 757 758 _ShowAlert(alertText, B_TRANSLATE("OK"), "", "", B_STOP_ALERT); 759 return status; 760 } 761 762 SetTitle(name); 763 764 if (fSaveMessage != message) { 765 delete fSaveMessage; 766 fSaveMessage = new BMessage(*message); 767 } 768 769 entry_ref ref; 770 if (entry.GetRef(&ref) == B_OK) 771 be_roster->AddToRecentDocuments(&ref, APP_SIGNATURE); 772 773 // clear clean modes 774 fSaveItem->SetEnabled(false); 775 fUndoCleans = false; 776 fRedoCleans = false; 777 fClean = true; 778 fNagOnNodeChange = true; 779 780 return status; 781 } 782 783 784 #undef B_TRANSLATION_CONTEXT 785 #define B_TRANSLATION_CONTEXT "Open_and_SaveAsPanel" 786 787 788 status_t 789 StyledEditWindow::SaveAs(BMessage* message) 790 { 791 if (fSavePanel == NULL) { 792 entry_ref* directory = NULL; 793 entry_ref dirRef; 794 if (fSaveMessage != NULL) { 795 if (fSaveMessage->FindRef("directory", &dirRef) == B_OK) 796 directory = &dirRef; 797 } 798 799 BMessenger target(this); 800 fSavePanel = new BFilePanel(B_SAVE_PANEL, &target, 801 directory, B_FILE_NODE, false); 802 803 BMenuBar* menuBar = dynamic_cast<BMenuBar*>( 804 fSavePanel->Window()->FindView("MenuBar")); 805 if (menuBar != NULL) { 806 fSavePanelEncodingMenu = new BMenu(B_TRANSLATE("Encoding")); 807 fSavePanelEncodingMenu->SetRadioMode(true); 808 menuBar->AddItem(fSavePanelEncodingMenu); 809 810 BCharacterSetRoster roster; 811 BCharacterSet charset; 812 while (roster.GetNextCharacterSet(&charset) == B_NO_ERROR) { 813 BString name(charset.GetPrintName()); 814 const char* mime = charset.GetMIMEName(); 815 if (mime) { 816 name.Append(" ("); 817 name.Append(mime); 818 name.Append(")"); 819 } 820 BMenuItem * item = new BMenuItem(name.String(), 821 new BMessage(SAVE_AS_ENCODING)); 822 item->SetTarget(this); 823 fSavePanelEncodingMenu->AddItem(item); 824 if (charset.GetFontID() == fTextView->GetEncoding()) 825 item->SetMarked(true); 826 } 827 } 828 } 829 830 fSavePanel->SetSaveText(Title()); 831 if (message != NULL) 832 fSavePanel->SetMessage(message); 833 834 fSavePanel->Show(); 835 return B_OK; 836 } 837 838 839 void 840 StyledEditWindow::OpenFile(entry_ref* ref) 841 { 842 if (_LoadFile(ref) != B_OK) { 843 fSaveItem->SetEnabled(true); 844 // allow saving new files 845 return; 846 } 847 848 be_roster->AddToRecentDocuments(ref, APP_SIGNATURE); 849 fSaveMessage = new BMessage(B_SAVE_REQUESTED); 850 if (fSaveMessage) { 851 BEntry entry(ref, true); 852 BEntry parent; 853 entry_ref parentRef; 854 char name[B_FILE_NAME_LENGTH]; 855 856 entry.GetParent(&parent); 857 entry.GetName(name); 858 parent.GetRef(&parentRef); 859 fSaveMessage->AddRef("directory", &parentRef); 860 fSaveMessage->AddString("name", name); 861 SetTitle(name); 862 863 _LoadAttrs(); 864 } 865 866 _SwitchNodeMonitor(true, ref); 867 868 fReloadItem->SetEnabled(fSaveMessage != NULL); 869 fEncodingItem->SetEnabled(fSaveMessage != NULL); 870 fTextView->Select(0, 0); 871 } 872 873 874 status_t 875 StyledEditWindow::PageSetup(const char* documentName) 876 { 877 BPrintJob printJob(documentName); 878 879 if (fPrintSettings != NULL) 880 printJob.SetSettings(new BMessage(*fPrintSettings)); 881 882 status_t result = printJob.ConfigPage(); 883 if (result == B_OK) { 884 delete fPrintSettings; 885 fPrintSettings = printJob.Settings(); 886 } 887 888 return result; 889 } 890 891 892 void 893 StyledEditWindow::Print(const char* documentName) 894 { 895 BPrintJob printJob(documentName); 896 if (fPrintSettings) 897 printJob.SetSettings(new BMessage(*fPrintSettings)); 898 899 if (printJob.ConfigJob() != B_OK) 900 return; 901 902 delete fPrintSettings; 903 fPrintSettings = printJob.Settings(); 904 905 // information from printJob 906 BRect printableRect = printJob.PrintableRect(); 907 int32 firstPage = printJob.FirstPage(); 908 int32 lastPage = printJob.LastPage(); 909 910 // lines eventually to be used to compute pages to print 911 int32 firstLine = 0; 912 int32 lastLine = fTextView->CountLines(); 913 914 // values to be computed 915 int32 pagesInDocument = 1; 916 int32 linesInDocument = fTextView->CountLines(); 917 918 int32 currentLine = 0; 919 while (currentLine < linesInDocument) { 920 float currentHeight = 0; 921 while (currentHeight < printableRect.Height() && currentLine 922 < linesInDocument) { 923 currentHeight += fTextView->LineHeight(currentLine); 924 if (currentHeight < printableRect.Height()) 925 currentLine++; 926 } 927 if (pagesInDocument == lastPage) 928 lastLine = currentLine - 1; 929 930 if (currentHeight >= printableRect.Height()) { 931 pagesInDocument++; 932 if (pagesInDocument == firstPage) 933 firstLine = currentLine; 934 } 935 } 936 937 if (lastPage > pagesInDocument - 1) { 938 lastPage = pagesInDocument - 1; 939 lastLine = currentLine - 1; 940 } 941 942 943 printJob.BeginJob(); 944 if (fTextView->CountLines() > 0 && fTextView->TextLength() > 0) { 945 int32 printLine = firstLine; 946 while (printLine <= lastLine) { 947 float currentHeight = 0; 948 int32 firstLineOnPage = printLine; 949 while (currentHeight < printableRect.Height() 950 && printLine <= lastLine) 951 { 952 currentHeight += fTextView->LineHeight(printLine); 953 if (currentHeight < printableRect.Height()) 954 printLine++; 955 } 956 957 float top = 0; 958 if (firstLineOnPage != 0) 959 top = fTextView->TextHeight(0, firstLineOnPage - 1); 960 961 float bottom = fTextView->TextHeight(0, printLine - 1); 962 BRect textRect(0.0, top + TEXT_INSET, 963 printableRect.Width(), bottom + TEXT_INSET); 964 printJob.DrawView(fTextView, textRect, B_ORIGIN); 965 printJob.SpoolPage(); 966 } 967 } 968 969 970 printJob.CommitJob(); 971 } 972 973 974 void 975 StyledEditWindow::SearchAllWindows(BString find, BString replace, 976 bool caseSensitive) 977 { 978 int32 numWindows; 979 numWindows = be_app->CountWindows(); 980 981 BMessage* message; 982 message= new BMessage(MSG_REPLACE_ALL); 983 message->AddString("FindText", find); 984 message->AddString("ReplaceText", replace); 985 message->AddBool("casesens", caseSensitive); 986 987 while (numWindows >= 0) { 988 StyledEditWindow* window = dynamic_cast<StyledEditWindow *>( 989 be_app->WindowAt(numWindows)); 990 991 BMessenger messenger(window); 992 messenger.SendMessage(message); 993 994 numWindows--; 995 } 996 } 997 998 999 bool 1000 StyledEditWindow::IsDocumentEntryRef(const entry_ref* ref) 1001 { 1002 if (ref == NULL) 1003 return false; 1004 1005 if (fSaveMessage == NULL) 1006 return false; 1007 1008 entry_ref dir; 1009 const char* name; 1010 if (fSaveMessage->FindRef("directory", &dir) != B_OK 1011 || fSaveMessage->FindString("name", &name) != B_OK) 1012 return false; 1013 1014 entry_ref documentRef; 1015 BPath documentPath(&dir); 1016 documentPath.Append(name); 1017 get_ref_for_path(documentPath.Path(), &documentRef); 1018 1019 return *ref == documentRef; 1020 } 1021 1022 1023 // #pragma mark - private methods 1024 1025 1026 #undef B_TRANSLATION_CONTEXT 1027 #define B_TRANSLATION_CONTEXT "Menus" 1028 1029 1030 void 1031 StyledEditWindow::_InitWindow(uint32 encoding) 1032 { 1033 fPrintSettings = NULL; 1034 fSaveMessage = NULL; 1035 1036 // undo modes 1037 fUndoFlag = false; 1038 fCanUndo = false; 1039 fRedoFlag = false; 1040 fCanRedo = false; 1041 1042 // clean modes 1043 fUndoCleans = false; 1044 fRedoCleans = false; 1045 fClean = true; 1046 1047 // search- state 1048 fReplaceString = ""; 1049 fStringToFind = ""; 1050 fCaseSensitive = false; 1051 fWrapAround = false; 1052 fBackSearch = false; 1053 1054 fNagOnNodeChange = true; 1055 1056 // add menubar 1057 fMenuBar = new BMenuBar(BRect(0, 0, 0, 0), "menubar"); 1058 AddChild(fMenuBar); 1059 1060 // add textview and scrollview 1061 1062 BRect viewFrame = Bounds(); 1063 viewFrame.top = fMenuBar->Bounds().Height() + 1; 1064 viewFrame.right -= B_V_SCROLL_BAR_WIDTH; 1065 viewFrame.left = 0; 1066 viewFrame.bottom -= B_H_SCROLL_BAR_HEIGHT; 1067 1068 BRect textBounds = viewFrame; 1069 textBounds.OffsetTo(B_ORIGIN); 1070 textBounds.InsetBy(TEXT_INSET, TEXT_INSET); 1071 1072 fTextView= new StyledEditView(viewFrame, textBounds, this); 1073 fTextView->SetDoesUndo(true); 1074 fTextView->SetStylable(true); 1075 fTextView->SetEncoding(encoding); 1076 1077 fScrollView = new BScrollView("scrollview", fTextView, B_FOLLOW_ALL, 0, 1078 true, true, B_PLAIN_BORDER); 1079 AddChild(fScrollView); 1080 fTextView->MakeFocus(true); 1081 1082 fStatusView = new StatusView(fScrollView); 1083 fScrollView->AddChild(fStatusView); 1084 1085 // Add "File"-menu: 1086 BMenu* menu = new BMenu(B_TRANSLATE("File")); 1087 fMenuBar->AddItem(menu); 1088 1089 BMenuItem* menuItem; 1090 menu->AddItem(menuItem = new BMenuItem(B_TRANSLATE("New"), 1091 new BMessage(MENU_NEW), 'N')); 1092 menuItem->SetTarget(be_app); 1093 1094 menu->AddItem(menuItem = new BMenuItem(fRecentMenu 1095 = new BMenu(B_TRANSLATE("Open" B_UTF8_ELLIPSIS)), 1096 new BMessage(MENU_OPEN))); 1097 menuItem->SetShortcut('O', 0); 1098 menuItem->SetTarget(be_app); 1099 menu->AddSeparatorItem(); 1100 1101 menu->AddItem(fSaveItem = new BMenuItem(B_TRANSLATE("Save"), 1102 new BMessage(MENU_SAVE), 'S')); 1103 fSaveItem->SetEnabled(false); 1104 menu->AddItem(menuItem = new BMenuItem( 1105 B_TRANSLATE("Save as" B_UTF8_ELLIPSIS), new BMessage(MENU_SAVEAS))); 1106 menuItem->SetShortcut('S', B_SHIFT_KEY); 1107 menuItem->SetEnabled(true); 1108 1109 menu->AddItem(fReloadItem 1110 = new BMenuItem(B_TRANSLATE("Reload" B_UTF8_ELLIPSIS), 1111 new BMessage(MENU_RELOAD), 'L')); 1112 fReloadItem->SetEnabled(false); 1113 1114 menu->AddItem(new BMenuItem(B_TRANSLATE("Close"), 1115 new BMessage(MENU_CLOSE), 'W')); 1116 1117 menu->AddSeparatorItem(); 1118 menu->AddItem(new BMenuItem(B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS), 1119 new BMessage(MENU_PAGESETUP))); 1120 menu->AddItem(new BMenuItem(B_TRANSLATE("Print" B_UTF8_ELLIPSIS), 1121 new BMessage(MENU_PRINT), 'P')); 1122 1123 menu->AddSeparatorItem(); 1124 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), 1125 new BMessage(MENU_QUIT), 'Q')); 1126 1127 // Add the "Edit"-menu: 1128 menu = new BMenu(B_TRANSLATE("Edit")); 1129 fMenuBar->AddItem(menu); 1130 1131 menu->AddItem(fUndoItem = new BMenuItem(B_TRANSLATE("Can't undo"), 1132 new BMessage(B_UNDO), 'Z')); 1133 fUndoItem->SetEnabled(false); 1134 1135 menu->AddSeparatorItem(); 1136 menu->AddItem(fCutItem = new BMenuItem(B_TRANSLATE("Cut"), 1137 new BMessage(B_CUT), 'X')); 1138 fCutItem->SetEnabled(false); 1139 fCutItem->SetTarget(fTextView); 1140 1141 menu->AddItem(fCopyItem = new BMenuItem(B_TRANSLATE("Copy"), 1142 new BMessage(B_COPY), 'C')); 1143 fCopyItem->SetEnabled(false); 1144 fCopyItem->SetTarget(fTextView); 1145 1146 menu->AddItem(menuItem = new BMenuItem(B_TRANSLATE("Paste"), 1147 new BMessage(B_PASTE), 'V')); 1148 menuItem->SetTarget(fTextView); 1149 1150 menu->AddSeparatorItem(); 1151 menu->AddItem(menuItem = new BMenuItem(B_TRANSLATE("Select all"), 1152 new BMessage(B_SELECT_ALL), 'A')); 1153 menuItem->SetTarget(fTextView); 1154 1155 menu->AddSeparatorItem(); 1156 menu->AddItem(new BMenuItem(B_TRANSLATE("Find" B_UTF8_ELLIPSIS), 1157 new BMessage(MENU_FIND), 'F')); 1158 menu->AddItem(fFindAgainItem= new BMenuItem(B_TRANSLATE("Find again"), 1159 new BMessage(MENU_FIND_AGAIN), 'G')); 1160 fFindAgainItem->SetEnabled(false); 1161 1162 menu->AddItem(new BMenuItem(B_TRANSLATE("Find selection"), 1163 new BMessage(MENU_FIND_SELECTION), 'H')); 1164 menu->AddItem(fReplaceItem = new BMenuItem(B_TRANSLATE("Replace" B_UTF8_ELLIPSIS), 1165 new BMessage(MENU_REPLACE), 'R')); 1166 menu->AddItem(fReplaceSameItem = new BMenuItem(B_TRANSLATE("Replace next"), 1167 new BMessage(MENU_REPLACE_SAME), 'T')); 1168 fReplaceSameItem->SetEnabled(false); 1169 1170 // Add the "Font"-menu: 1171 fFontMenu = new BMenu(B_TRANSLATE("Font")); 1172 fMenuBar->AddItem(fFontMenu); 1173 1174 // "Size"-subMenu 1175 fFontSizeMenu = new BMenu(B_TRANSLATE("Size")); 1176 fFontSizeMenu->SetRadioMode(true); 1177 fFontMenu->AddItem(fFontSizeMenu); 1178 1179 const int32 fontSizes[] = {9, 10, 11, 12, 14, 18, 24, 36, 48, 72}; 1180 for (uint32 i = 0; i < sizeof(fontSizes) / sizeof(fontSizes[0]); i++) { 1181 BMessage* fontMessage = new BMessage(FONT_SIZE); 1182 fontMessage->AddFloat("size", fontSizes[i]); 1183 1184 char label[64]; 1185 snprintf(label, sizeof(label), "%" B_PRId32, fontSizes[i]); 1186 fFontSizeMenu->AddItem(menuItem = new BMenuItem(label, fontMessage)); 1187 1188 if (fontSizes[i] == (int32)be_plain_font->Size()) 1189 menuItem->SetMarked(true); 1190 } 1191 1192 // "Color"-subMenu 1193 fFontColorMenu = new BMenu(B_TRANSLATE("Color")); 1194 fFontColorMenu->SetRadioMode(true); 1195 fFontMenu->AddItem(fFontColorMenu); 1196 1197 fFontColorMenu->AddItem(fBlackItem = new BMenuItem(B_TRANSLATE("Black"), 1198 new BMessage(FONT_COLOR))); 1199 fBlackItem->SetMarked(true); 1200 fFontColorMenu->AddItem(fRedItem = new ColorMenuItem(B_TRANSLATE("Red"), 1201 RED, new BMessage(FONT_COLOR))); 1202 fFontColorMenu->AddItem(fGreenItem = new ColorMenuItem(B_TRANSLATE("Green"), 1203 GREEN, new BMessage(FONT_COLOR))); 1204 fFontColorMenu->AddItem(fBlueItem = new ColorMenuItem(B_TRANSLATE("Blue"), 1205 BLUE, new BMessage(FONT_COLOR))); 1206 fFontColorMenu->AddItem(fCyanItem = new ColorMenuItem(B_TRANSLATE("Cyan"), 1207 CYAN, new BMessage(FONT_COLOR))); 1208 fFontColorMenu->AddItem(fMagentaItem = new ColorMenuItem(B_TRANSLATE("Magenta"), 1209 MAGENTA, new BMessage(FONT_COLOR))); 1210 fFontColorMenu->AddItem(fYellowItem = new ColorMenuItem(B_TRANSLATE("Yellow"), 1211 YELLOW, new BMessage(FONT_COLOR))); 1212 fFontMenu->AddSeparatorItem(); 1213 1214 // "Bold" & "Italic" menu items 1215 fFontMenu->AddItem(fBoldItem = new BMenuItem(B_TRANSLATE("Bold"), 1216 new BMessage(kMsgSetBold))); 1217 fFontMenu->AddItem(fItalicItem = new BMenuItem(B_TRANSLATE("Italic"), 1218 new BMessage(kMsgSetItalic))); 1219 fBoldItem->SetShortcut('B', 0); 1220 fItalicItem->SetShortcut('I', 0); 1221 fFontMenu->AddSeparatorItem(); 1222 1223 // Available fonts 1224 1225 fCurrentFontItem = 0; 1226 fCurrentStyleItem = 0; 1227 1228 BMenu* subMenu; 1229 int32 numFamilies = count_font_families(); 1230 for (int32 i = 0; i < numFamilies; i++) { 1231 font_family family; 1232 if (get_font_family(i, &family) == B_OK) { 1233 subMenu = new BMenu(family); 1234 subMenu->SetRadioMode(true); 1235 fFontMenu->AddItem(new BMenuItem(subMenu, 1236 new BMessage(FONT_FAMILY))); 1237 1238 int32 numStyles = count_font_styles(family); 1239 for (int32 j = 0; j < numStyles; j++) { 1240 font_style style; 1241 uint32 flags; 1242 if (get_font_style(family, j, &style, &flags) == B_OK) { 1243 subMenu->AddItem(new BMenuItem(style, 1244 new BMessage(FONT_STYLE))); 1245 } 1246 } 1247 } 1248 } 1249 1250 // Add the "Document"-menu: 1251 menu = new BMenu(B_TRANSLATE("Document")); 1252 fMenuBar->AddItem(menu); 1253 1254 // "Align"-subMenu: 1255 subMenu = new BMenu(B_TRANSLATE("Align")); 1256 subMenu->SetRadioMode(true); 1257 1258 subMenu->AddItem(fAlignLeft = new BMenuItem(B_TRANSLATE("Left"), 1259 new BMessage(ALIGN_LEFT))); 1260 fAlignLeft->SetMarked(true); 1261 fAlignLeft->SetShortcut('L', B_OPTION_KEY); 1262 1263 subMenu->AddItem(fAlignCenter = new BMenuItem(B_TRANSLATE("Center"), 1264 new BMessage(ALIGN_CENTER))); 1265 fAlignCenter->SetShortcut('C', B_OPTION_KEY); 1266 1267 subMenu->AddItem(fAlignRight = new BMenuItem(B_TRANSLATE("Right"), 1268 new BMessage(ALIGN_RIGHT))); 1269 fAlignRight->SetShortcut('R', B_OPTION_KEY); 1270 1271 menu->AddItem(subMenu); 1272 menu->AddItem(fWrapItem = new BMenuItem(B_TRANSLATE("Wrap lines"), 1273 new BMessage(WRAP_LINES))); 1274 fWrapItem->SetMarked(true); 1275 fWrapItem->SetShortcut('W', B_OPTION_KEY); 1276 1277 menu->AddItem(fEncodingItem = _MakeEncodingMenuItem()); 1278 fEncodingItem->SetEnabled(false); 1279 1280 menu->AddSeparatorItem(); 1281 menu->AddItem(new BMenuItem(B_TRANSLATE("Statistics" B_UTF8_ELLIPSIS), 1282 new BMessage(SHOW_STATISTICS))); 1283 1284 fSavePanel = NULL; 1285 fSavePanelEncodingMenu = NULL; 1286 // build lazily 1287 } 1288 1289 1290 void 1291 StyledEditWindow::_LoadAttrs() 1292 { 1293 entry_ref dir; 1294 const char* name; 1295 if (fSaveMessage->FindRef("directory", &dir) != B_OK 1296 || fSaveMessage->FindString("name", &name) != B_OK) 1297 return; 1298 1299 BPath documentPath(&dir); 1300 documentPath.Append(name); 1301 1302 BNode documentNode(documentPath.Path()); 1303 if (documentNode.InitCheck() != B_OK) 1304 return; 1305 1306 BRect newFrame; 1307 ssize_t bytesRead = documentNode.ReadAttr(kInfoAttributeName, B_RECT_TYPE, 1308 0, &newFrame, sizeof(BRect)); 1309 if (bytesRead != sizeof(BRect)) 1310 return; 1311 1312 swap_data(B_RECT_TYPE, &newFrame, sizeof(BRect), B_SWAP_BENDIAN_TO_HOST); 1313 1314 // Check if the frame in on screen, otherwise, ignore it 1315 BScreen screen(this); 1316 if (newFrame.Width() > 32 && newFrame.Height() > 32 1317 && screen.Frame().Contains(newFrame)) { 1318 MoveTo(newFrame.left, newFrame.top); 1319 ResizeTo(newFrame.Width(), newFrame.Height()); 1320 } 1321 } 1322 1323 1324 void 1325 StyledEditWindow::_SaveAttrs() 1326 { 1327 if (!fSaveMessage) 1328 return; 1329 1330 entry_ref dir; 1331 const char* name; 1332 if (fSaveMessage->FindRef("directory", &dir) != B_OK 1333 || fSaveMessage->FindString("name", &name) != B_OK) 1334 return; 1335 1336 BPath documentPath(&dir); 1337 documentPath.Append(name); 1338 1339 BNode documentNode(documentPath.Path()); 1340 if (documentNode.InitCheck() != B_OK) 1341 return; 1342 1343 BRect frame(Frame()); 1344 swap_data(B_RECT_TYPE, &frame, sizeof(BRect), B_SWAP_HOST_TO_BENDIAN); 1345 1346 documentNode.WriteAttr(kInfoAttributeName, B_RECT_TYPE, 0, &frame, 1347 sizeof(BRect)); 1348 } 1349 1350 1351 #undef B_TRANSLATION_CONTEXT 1352 #define B_TRANSLATION_CONTEXT "LoadAlert" 1353 1354 1355 status_t 1356 StyledEditWindow::_LoadFile(entry_ref* ref, const char* forceEncoding) 1357 { 1358 BEntry entry(ref, true); 1359 // traverse an eventual link 1360 1361 status_t status = entry.InitCheck(); 1362 if (status == B_OK && entry.IsDirectory()) 1363 status = B_IS_A_DIRECTORY; 1364 1365 BFile file; 1366 if (status == B_OK) 1367 status = file.SetTo(&entry, B_READ_ONLY); 1368 if (status == B_OK) 1369 status = fTextView->GetStyledText(&file, forceEncoding); 1370 1371 if (status == B_ENTRY_NOT_FOUND) { 1372 // Treat non-existing files consideratley; we just want to get an 1373 // empty window for them - to create this new document 1374 status = B_OK; 1375 } 1376 1377 if (status != B_OK) { 1378 // If an error occured, bail out and tell the user what happened 1379 BEntry entry(ref, true); 1380 char name[B_FILE_NAME_LENGTH]; 1381 if (entry.GetName(name) != B_OK) 1382 strlcpy(name, B_TRANSLATE("???"), sizeof(name)); 1383 1384 BString text; 1385 if (status == B_BAD_TYPE) 1386 bs_printf(&text, 1387 B_TRANSLATE("Error loading \"%s\":\n\tUnsupported format"), name); 1388 else 1389 bs_printf(&text, B_TRANSLATE("Error loading \"%s\":\n\t%s"), 1390 name, strerror(status)); 1391 1392 _ShowAlert(text, B_TRANSLATE("OK"), "", "", B_STOP_ALERT); 1393 return status; 1394 } 1395 1396 struct stat st; 1397 if (file.InitCheck() == B_OK && file.GetStat(&st) == B_OK) { 1398 bool editable = (getuid() == st.st_uid && S_IWUSR & st.st_mode) 1399 || (getgid() == st.st_gid && S_IWGRP & st.st_mode) 1400 || (S_IWOTH & st.st_mode); 1401 _SetReadOnly(!editable); 1402 } 1403 1404 // update alignment 1405 switch (fTextView->Alignment()) { 1406 case B_ALIGN_LEFT: 1407 default: 1408 fAlignLeft->SetMarked(true); 1409 break; 1410 case B_ALIGN_CENTER: 1411 fAlignCenter->SetMarked(true); 1412 break; 1413 case B_ALIGN_RIGHT: 1414 fAlignRight->SetMarked(true); 1415 break; 1416 } 1417 1418 // update word wrapping 1419 fWrapItem->SetMarked(fTextView->DoesWordWrap()); 1420 return B_OK; 1421 } 1422 1423 1424 #undef B_TRANSLATION_CONTEXT 1425 #define B_TRANSLATION_CONTEXT "RevertToSavedAlert" 1426 1427 1428 void 1429 StyledEditWindow::_ReloadDocument(BMessage* message) 1430 { 1431 entry_ref ref; 1432 const char* name; 1433 1434 if (fSaveMessage == NULL || message == NULL) 1435 return; 1436 1437 fSaveMessage->FindRef("directory", &ref); 1438 fSaveMessage->FindString("name", &name); 1439 1440 BDirectory dir(&ref); 1441 status_t status = dir.InitCheck(); 1442 BEntry entry; 1443 if (status == B_OK) 1444 status = entry.SetTo(&dir, name); 1445 1446 if (status == B_OK) 1447 status = entry.GetRef(&ref); 1448 1449 if (status != B_OK || !entry.Exists()) { 1450 BString alertText; 1451 bs_printf(&alertText, 1452 B_TRANSLATE("Cannot revert, file not found: \"%s\"."), name); 1453 _ShowAlert(alertText, B_TRANSLATE("OK"), "", "", B_STOP_ALERT); 1454 return; 1455 } 1456 1457 if (!fClean) { 1458 BString alertText; 1459 bs_printf(&alertText, 1460 B_TRANSLATE("\"%s\" has unsaved changes.\n" 1461 "Revert it to the last saved version? "), Title()); 1462 if (_ShowAlert(alertText, B_TRANSLATE("Cancel"), B_TRANSLATE("OK"), 1463 "", B_WARNING_ALERT) != 1) 1464 return; 1465 } 1466 1467 const char* forceEncoding = NULL; 1468 if (message->FindString("encoding", &forceEncoding) != B_OK) { 1469 const BCharacterSet* charset 1470 = BCharacterSetRoster::GetCharacterSetByFontID( 1471 fTextView->GetEncoding()); 1472 if (charset != NULL) 1473 forceEncoding = charset->GetName(); 1474 } 1475 1476 BScrollBar* vertBar = fScrollView->ScrollBar(B_VERTICAL); 1477 float vertPos = vertBar != NULL ? vertBar->Value() : 0.f; 1478 1479 DisableUpdates(); 1480 1481 fTextView->Reset(); 1482 1483 status = _LoadFile(&ref, forceEncoding); 1484 1485 if (vertBar != NULL) 1486 vertBar->SetValue(vertPos); 1487 1488 EnableUpdates(); 1489 1490 if (status != B_OK) 1491 return; 1492 1493 #undef B_TRANSLATION_CONTEXT 1494 #define B_TRANSLATION_CONTEXT "Menus" 1495 1496 // clear undo modes 1497 fUndoItem->SetLabel(B_TRANSLATE("Can't undo")); 1498 fUndoItem->SetEnabled(false); 1499 fUndoFlag = false; 1500 fCanUndo = false; 1501 fRedoFlag = false; 1502 fCanRedo = false; 1503 1504 // clear clean modes 1505 fSaveItem->SetEnabled(false); 1506 1507 fUndoCleans = false; 1508 fRedoCleans = false; 1509 fClean = true; 1510 1511 fNagOnNodeChange = true; 1512 } 1513 1514 1515 status_t 1516 StyledEditWindow::_UnlockFile() 1517 { 1518 _NodeMonitorSuspender nodeMonitorSuspender(this); 1519 1520 if (!fSaveMessage) 1521 return B_ERROR; 1522 1523 entry_ref dirRef; 1524 const char* name; 1525 if (fSaveMessage->FindRef("directory", &dirRef) != B_OK 1526 || fSaveMessage->FindString("name", &name) != B_OK) 1527 return B_BAD_VALUE; 1528 1529 BDirectory dir(&dirRef); 1530 BEntry entry(&dir, name); 1531 1532 status_t status = dir.InitCheck(); 1533 if (status != B_OK) 1534 return status; 1535 1536 status = entry.InitCheck(); 1537 if (status != B_OK) 1538 return status; 1539 1540 struct stat st; 1541 BFile file(&entry, B_READ_WRITE); 1542 status = file.InitCheck(); 1543 if (status != B_OK) 1544 return status; 1545 1546 status = file.GetStat(&st); 1547 if (status != B_OK) 1548 return status; 1549 1550 st.st_mode |= S_IWUSR; 1551 status = file.SetPermissions(st.st_mode); 1552 if (status == B_OK) 1553 _SetReadOnly(false); 1554 1555 return status; 1556 } 1557 1558 1559 bool 1560 StyledEditWindow::_Search(BString string, bool caseSensitive, bool wrap, 1561 bool backSearch, bool scrollToOccurence) 1562 { 1563 int32 start; 1564 int32 finish; 1565 1566 start = B_ERROR; 1567 1568 int32 length = string.Length(); 1569 if (length == 0) 1570 return false; 1571 1572 BString viewText(fTextView->Text()); 1573 int32 textStart, textFinish; 1574 fTextView->GetSelection(&textStart, &textFinish); 1575 if (backSearch) { 1576 if (caseSensitive) 1577 start = viewText.FindLast(string, textStart); 1578 else 1579 start = viewText.IFindLast(string, textStart); 1580 } else { 1581 if (caseSensitive) 1582 start = viewText.FindFirst(string, textFinish); 1583 else 1584 start = viewText.IFindFirst(string, textFinish); 1585 } 1586 if (start == B_ERROR && wrap) { 1587 if (backSearch) { 1588 if (caseSensitive) 1589 start = viewText.FindLast(string, viewText.Length()); 1590 else 1591 start = viewText.IFindLast(string, viewText.Length()); 1592 } else { 1593 if (caseSensitive) 1594 start = viewText.FindFirst(string, 0); 1595 else 1596 start = viewText.IFindFirst(string, 0); 1597 } 1598 } 1599 1600 if (start != B_ERROR) { 1601 finish = start + length; 1602 fTextView->Select(start, finish); 1603 1604 if (scrollToOccurence) 1605 fTextView->ScrollToSelection(); 1606 return true; 1607 } 1608 1609 return false; 1610 } 1611 1612 1613 void 1614 StyledEditWindow::_FindSelection() 1615 { 1616 int32 selectionStart, selectionFinish; 1617 fTextView->GetSelection(&selectionStart, &selectionFinish); 1618 1619 int32 selectionLength = selectionFinish- selectionStart; 1620 1621 BString viewText = fTextView->Text(); 1622 viewText.CopyInto(fStringToFind, selectionStart, selectionLength); 1623 fFindAgainItem->SetEnabled(true); 1624 _Search(fStringToFind, fCaseSensitive, fWrapAround, fBackSearch); 1625 } 1626 1627 1628 bool 1629 StyledEditWindow::_Replace(BString findThis, BString replaceWith, 1630 bool caseSensitive, bool wrap, bool backSearch) 1631 { 1632 if (_Search(findThis, caseSensitive, wrap, backSearch)) { 1633 int32 start; 1634 int32 finish; 1635 fTextView->GetSelection(&start, &finish); 1636 1637 _UpdateCleanUndoRedoSaveRevert(); 1638 fTextView->SetSuppressChanges(true); 1639 fTextView->Delete(start, start + findThis.Length()); 1640 fTextView->Insert(start, replaceWith.String(), replaceWith.Length()); 1641 fTextView->SetSuppressChanges(false); 1642 fTextView->Select(start, start + replaceWith.Length()); 1643 fTextView->ScrollToSelection(); 1644 return true; 1645 } 1646 1647 return false; 1648 } 1649 1650 1651 void 1652 StyledEditWindow::_ReplaceAll(BString findThis, BString replaceWith, 1653 bool caseSensitive) 1654 { 1655 bool first = true; 1656 fTextView->SetSuppressChanges(true); 1657 1658 // start from the beginning of text 1659 fTextView->Select(0, 0); 1660 1661 // iterate occurences of findThis without wrapping around 1662 while (_Search(findThis, caseSensitive, false, false, false)) { 1663 if (first) { 1664 _UpdateCleanUndoRedoSaveRevert(); 1665 first = false; 1666 } 1667 int32 start; 1668 int32 finish; 1669 1670 fTextView->GetSelection(&start, &finish); 1671 fTextView->Delete(start, start + findThis.Length()); 1672 fTextView->Insert(start, replaceWith.String(), replaceWith.Length()); 1673 1674 // advance the caret behind the inserted text 1675 start += replaceWith.Length(); 1676 fTextView->Select(start, start); 1677 } 1678 fTextView->ScrollToSelection(); 1679 fTextView->SetSuppressChanges(false); 1680 } 1681 1682 1683 void 1684 StyledEditWindow::_SetFontSize(float fontSize) 1685 { 1686 uint32 sameProperties; 1687 BFont font; 1688 1689 fTextView->GetFontAndColor(&font, &sameProperties); 1690 font.SetSize(fontSize); 1691 fTextView->SetFontAndColor(&font, B_FONT_SIZE); 1692 1693 _UpdateCleanUndoRedoSaveRevert(); 1694 } 1695 1696 1697 void 1698 StyledEditWindow::_SetFontColor(const rgb_color* color) 1699 { 1700 uint32 sameProperties; 1701 BFont font; 1702 1703 fTextView->GetFontAndColor(&font, &sameProperties, NULL, NULL); 1704 fTextView->SetFontAndColor(&font, 0, color); 1705 1706 _UpdateCleanUndoRedoSaveRevert(); 1707 } 1708 1709 1710 void 1711 StyledEditWindow::_SetFontStyle(const char* fontFamily, const char* fontStyle) 1712 { 1713 BFont font; 1714 uint32 sameProperties; 1715 1716 // find out what the old font was 1717 font_family oldFamily; 1718 font_style oldStyle; 1719 fTextView->GetFontAndColor(&font, &sameProperties); 1720 font.GetFamilyAndStyle(&oldFamily, &oldStyle); 1721 1722 // clear that family's bit on the menu, if necessary 1723 if (strcmp(oldFamily, fontFamily)) { 1724 BMenuItem* oldItem = fFontMenu->FindItem(oldFamily); 1725 if (oldItem != NULL) { 1726 oldItem->SetMarked(false); 1727 BMenu* menu = oldItem->Submenu(); 1728 if (menu != NULL) { 1729 oldItem = menu->FindItem(oldStyle); 1730 if (oldItem != NULL) 1731 oldItem->SetMarked(false); 1732 } 1733 } 1734 } 1735 1736 font.SetFamilyAndStyle(fontFamily, fontStyle); 1737 1738 uint16 face = 0; 1739 1740 if (!(font.Face() & B_REGULAR_FACE)) 1741 face = font.Face(); 1742 1743 if (fBoldItem->IsMarked()) 1744 face |= B_BOLD_FACE; 1745 1746 if (fItalicItem->IsMarked()) 1747 face |= B_ITALIC_FACE; 1748 1749 font.SetFace(face); 1750 1751 fTextView->SetFontAndColor(&font, B_FONT_FAMILY_AND_STYLE); 1752 1753 BMenuItem* superItem; 1754 superItem = fFontMenu->FindItem(fontFamily); 1755 if (superItem != NULL) { 1756 superItem->SetMarked(true); 1757 fCurrentFontItem = superItem; 1758 } 1759 1760 _UpdateCleanUndoRedoSaveRevert(); 1761 } 1762 1763 1764 #undef B_TRANSLATION_CONTEXT 1765 #define B_TRANSLATION_CONTEXT "Statistics" 1766 1767 1768 int32 1769 StyledEditWindow::_ShowStatistics() 1770 { 1771 size_t words = 0; 1772 bool inWord = false; 1773 size_t length = fTextView->TextLength(); 1774 1775 for (size_t i = 0; i < length; i++) { 1776 if (BUnicodeChar::IsSpace(fTextView->Text()[i])) { 1777 inWord = false; 1778 } else if (!inWord) { 1779 words++; 1780 inWord = true; 1781 } 1782 } 1783 1784 BString result; 1785 result << B_TRANSLATE("Document statistics") << '\n' << '\n' 1786 << B_TRANSLATE("Lines:") << ' ' << fTextView->CountLines() << '\n' 1787 << B_TRANSLATE("Characters:") << ' ' << length << '\n' 1788 << B_TRANSLATE("Words:") << ' ' << words; 1789 1790 BAlert* alert = new BAlert("Statistics", result, B_TRANSLATE("OK"), NULL, 1791 NULL, B_WIDTH_AS_USUAL, B_EVEN_SPACING, B_INFO_ALERT); 1792 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1793 1794 return alert->Go(); 1795 } 1796 1797 1798 void 1799 StyledEditWindow::_SetReadOnly(bool readOnly) 1800 { 1801 fReplaceItem->SetEnabled(!readOnly); 1802 fReplaceSameItem->SetEnabled(!readOnly); 1803 fFontMenu->SetEnabled(!readOnly); 1804 fAlignLeft->Menu()->SetEnabled(!readOnly); 1805 fWrapItem->SetEnabled(!readOnly); 1806 fTextView->MakeEditable(!readOnly); 1807 } 1808 1809 1810 #undef B_TRANSLATION_CONTEXT 1811 #define B_TRANSLATION_CONTEXT "Menus" 1812 1813 1814 void 1815 StyledEditWindow::_UpdateCleanUndoRedoSaveRevert() 1816 { 1817 fClean = false; 1818 fUndoCleans = false; 1819 fRedoCleans = false; 1820 fReloadItem->SetEnabled(fSaveMessage != NULL); 1821 fEncodingItem->SetEnabled(fSaveMessage != NULL); 1822 fSaveItem->SetEnabled(true); 1823 fUndoItem->SetLabel(B_TRANSLATE("Can't undo")); 1824 fUndoItem->SetEnabled(false); 1825 fCanUndo = false; 1826 fCanRedo = false; 1827 } 1828 1829 1830 int32 1831 StyledEditWindow::_ShowAlert(const BString& text, const BString& label, 1832 const BString& label2, const BString& label3, alert_type type) const 1833 { 1834 const char* button2 = NULL; 1835 if (label2.Length() > 0) 1836 button2 = label2.String(); 1837 1838 const char* button3 = NULL; 1839 button_spacing spacing = B_EVEN_SPACING; 1840 if (label3.Length() > 0) { 1841 button3 = label3.String(); 1842 spacing = B_OFFSET_SPACING; 1843 } 1844 1845 BAlert* alert = new BAlert("Alert", text.String(), label.String(), button2, 1846 button3, B_WIDTH_AS_USUAL, spacing, type); 1847 alert->SetShortcut(0, B_ESCAPE); 1848 1849 return alert->Go(); 1850 } 1851 1852 1853 BMenuItem* 1854 StyledEditWindow::_MakeEncodingMenuItem() 1855 { 1856 BMenu* menu = new BMenu(B_TRANSLATE("Text encoding")); 1857 1858 BCharacterSetRoster roster; 1859 BCharacterSet charset; 1860 while (roster.GetNextCharacterSet(&charset) == B_OK) { 1861 const char* mime = charset.GetMIMEName(); 1862 BString name(charset.GetPrintName()); 1863 1864 if (mime) 1865 name << " (" << mime << ")"; 1866 1867 BMessage *message = new BMessage(MENU_RELOAD); 1868 if (message != NULL) { 1869 message->AddString("encoding", charset.GetName()); 1870 menu->AddItem(new BMenuItem(name, message)); 1871 } 1872 } 1873 1874 menu->AddSeparatorItem(); 1875 BMessage *message = new BMessage(MENU_RELOAD); 1876 message->AddString("encoding", "auto"); 1877 menu->AddItem(new BMenuItem(B_TRANSLATE("Autodetect"), message)); 1878 1879 menu->SetRadioMode(true); 1880 1881 return new BMenuItem(menu, new BMessage(MENU_RELOAD)); 1882 } 1883 1884 1885 #undef B_TRANSLATION_CONTEXT 1886 #define B_TRANSLATION_CONTEXT "NodeMonitorAlerts" 1887 1888 1889 void 1890 StyledEditWindow::_ShowNodeChangeAlert(const char* name, bool removed) 1891 { 1892 if (!fNagOnNodeChange) 1893 return; 1894 1895 BString alertText(removed ? B_TRANSLATE("File \"%file%\" was removed by " 1896 "another application, recover it?") 1897 : B_TRANSLATE("File \"%file%\" was modified by " 1898 "another application, reload it?")); 1899 alertText.ReplaceAll("%file%", name); 1900 1901 if (_ShowAlert(alertText, removed ? B_TRANSLATE("Recover") 1902 : B_TRANSLATE("Reload"), B_TRANSLATE("Ignore"), "", 1903 B_WARNING_ALERT) == 0) 1904 { 1905 if (!removed) { 1906 // supress the warning - user has already agreed 1907 fClean = true; 1908 BMessage msg(MENU_RELOAD); 1909 _ReloadDocument(&msg); 1910 } else 1911 Save(); 1912 } else 1913 fNagOnNodeChange = false; 1914 1915 fSaveItem->SetEnabled(!fClean); 1916 } 1917 1918 1919 void 1920 StyledEditWindow::_HandleNodeMonitorEvent(BMessage *message) 1921 { 1922 int32 opcode = 0; 1923 if (message->FindInt32("opcode", &opcode) != B_OK) 1924 return; 1925 1926 if (opcode != B_ENTRY_CREATED 1927 && message->FindInt64("node") != fNodeRef.node) 1928 // bypass foreign nodes' event 1929 return; 1930 1931 switch (opcode) { 1932 case B_STAT_CHANGED: 1933 { 1934 int32 fields = 0; 1935 if (message->FindInt32("fields", &fields) == B_OK 1936 && (fields & (B_STAT_SIZE | B_STAT_MODIFICATION_TIME 1937 | B_STAT_MODE)) == 0) 1938 break; 1939 1940 const char* name = NULL; 1941 if (fSaveMessage->FindString("name", &name) != B_OK) 1942 break; 1943 1944 _ShowNodeChangeAlert(name, false); 1945 } 1946 break; 1947 1948 case B_ENTRY_MOVED: 1949 { 1950 int32 device = 0; 1951 int64 srcFolder = 0; 1952 int64 dstFolder = 0; 1953 const char* name = NULL; 1954 if (message->FindInt32("device", &device) != B_OK 1955 || message->FindInt64("to directory", &dstFolder) != B_OK 1956 || message->FindInt64("from directory", &srcFolder) != B_OK 1957 || message->FindString("name", &name) != B_OK) 1958 break; 1959 1960 entry_ref newRef(device, dstFolder, name); 1961 BEntry entry(&newRef); 1962 1963 BEntry dirEntry; 1964 entry.GetParent(&dirEntry); 1965 1966 entry_ref ref; 1967 dirEntry.GetRef(&ref); 1968 fSaveMessage->ReplaceRef("directory", &ref); 1969 fSaveMessage->ReplaceString("name", name); 1970 1971 // store previous name - it may be useful in case 1972 // we have just moved to temporary copy of file (vim case) 1973 const char* sourceName = NULL; 1974 if (message->FindString("from name", &sourceName) == B_OK) { 1975 fSaveMessage->RemoveName("org.name"); 1976 fSaveMessage->AddString("org.name", sourceName); 1977 fSaveMessage->RemoveName("move time"); 1978 fSaveMessage->AddInt64("move time", system_time()); 1979 } 1980 1981 SetTitle(name); 1982 be_roster->AddToRecentDocuments(&newRef, APP_SIGNATURE); 1983 1984 if (srcFolder != dstFolder) { 1985 _SwitchNodeMonitor(false); 1986 _SwitchNodeMonitor(true); 1987 } 1988 } 1989 break; 1990 1991 case B_ENTRY_REMOVED: 1992 { 1993 _SwitchNodeMonitor(false); 1994 1995 fClean = false; 1996 1997 // some editors like vim save files in following way: 1998 // 1) move t.txt -> t.txt~ 1999 // 2) re-create t.txt and write data to it 2000 // 3) remove t.txt~ 2001 // go to catch this case 2002 int32 device = 0; 2003 int64 directory = 0; 2004 BString orgName; 2005 if (fSaveMessage->FindString("org.name", &orgName) == B_OK 2006 && message->FindInt32("device", &device) == B_OK 2007 && message->FindInt64("directory", &directory) == B_OK) 2008 { 2009 // reuse the source name if it is not too old 2010 bigtime_t time = fSaveMessage->FindInt64("move time"); 2011 if ((system_time() - time) < 1000000) { 2012 entry_ref ref(device, directory, orgName); 2013 BEntry entry(&ref); 2014 if (entry.InitCheck() == B_OK) { 2015 _SwitchNodeMonitor(true, &ref); 2016 } 2017 2018 fSaveMessage->ReplaceString("name", orgName); 2019 fSaveMessage->RemoveName("org.name"); 2020 fSaveMessage->RemoveName("move time"); 2021 2022 SetTitle(orgName); 2023 _ShowNodeChangeAlert(orgName, false); 2024 break; 2025 } 2026 } 2027 2028 const char* name = NULL; 2029 if (message->FindString("name", &name) != B_OK 2030 && fSaveMessage->FindString("name", &name) != B_OK) 2031 name = "Unknown"; 2032 2033 _ShowNodeChangeAlert(name, true); 2034 } 2035 break; 2036 2037 default: 2038 break; 2039 } 2040 } 2041 2042 2043 void 2044 StyledEditWindow::_SwitchNodeMonitor(bool on, entry_ref* ref) 2045 { 2046 if (!on) { 2047 watch_node(&fNodeRef, B_STOP_WATCHING, this); 2048 watch_node(&fFolderNodeRef, B_STOP_WATCHING, this); 2049 fNodeRef = node_ref(); 2050 fFolderNodeRef = node_ref(); 2051 return; 2052 } 2053 2054 BEntry entry, folderEntry; 2055 2056 if (ref != NULL) { 2057 entry.SetTo(ref, true); 2058 entry.GetParent(&folderEntry); 2059 2060 } else if (fSaveMessage != NULL) { 2061 entry_ref ref; 2062 const char* name = NULL; 2063 if (fSaveMessage->FindRef("directory", &ref) != B_OK 2064 || fSaveMessage->FindString("name", &name) != B_OK) 2065 return; 2066 2067 BDirectory dir(&ref); 2068 entry.SetTo(&dir, name); 2069 folderEntry.SetTo(&ref); 2070 2071 } else 2072 return; 2073 2074 if (entry.InitCheck() != B_OK || folderEntry.InitCheck() != B_OK) 2075 return; 2076 2077 entry.GetNodeRef(&fNodeRef); 2078 folderEntry.GetNodeRef(&fFolderNodeRef); 2079 2080 watch_node(&fNodeRef, B_WATCH_STAT, this); 2081 watch_node(&fFolderNodeRef, B_WATCH_DIRECTORY, this); 2082 } 2083