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