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