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