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