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