xref: /haiku/src/apps/text_search/GrepWindow.cpp (revision 16ad15142c48ee36cd6a807a24efc99c88d4310d)
1 /*
2  * Copyright (c) 1998-2007 Matthijs Hollemans
3  * All rights reserved. Distributed under the terms of the MIT License.
4  */
5 #include "GrepWindow.h"
6 
7 #include <ctype.h>
8 #include <errno.h>
9 #include <new>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <unistd.h>
14 
15 #include <Application.h>
16 #include <AppFileInfo.h>
17 #include <Alert.h>
18 #include <Clipboard.h>
19 #include <LayoutBuilder.h>
20 #include <MessageRunner.h>
21 #include <MimeType.h>
22 #include <Path.h>
23 #include <PathMonitor.h>
24 #include <Roster.h>
25 #include <SpaceLayoutItem.h>
26 #include <String.h>
27 #include <UTF8.h>
28 
29 #include "ChangesIterator.h"
30 #include "GlobalDefs.h"
31 #include "Grepper.h"
32 #include "InitialIterator.h"
33 
34 #undef B_TRANSLATION_CONTEXT
35 #define B_TRANSLATION_CONTEXT "GrepWindow"
36 
37 
38 const char* kAppName = B_TRANSLATE_MARK_SYSTEM_NAME("TextSearch");
39 
40 
41 using std::nothrow;
42 
43 static const bigtime_t kChangesPulseInterval = 150000;
44 
45 #define TRACE_NODE_MONITORING
46 #ifdef TRACE_NODE_MONITORING
47 # define TRACE_NM(x...) printf(x)
48 #else
49 # define TRACE_NM(x...)
50 #endif
51 
52 //#define TRACE_FUNCTIONS
53 #ifdef TRACE_FUNCTIONS
54 	class FunctionTracer {
55 	public:
56 		FunctionTracer(const char* functionName)
57 			: fName(functionName)
58 		{
59 			printf("%s - enter\n", fName.String());
60 		}
61 		~FunctionTracer()
62 		{
63 			printf("%s - exit\n", fName.String());
64 		}
65 	private:
66 		BString	fName;
67 	};
68 # define CALLED()	FunctionTracer functionTracer(__PRETTY_FUNCTION__)
69 #else
70 # define CALLED()
71 #endif // TRACE_FUNCTIONS
72 
73 
74 GrepWindow::GrepWindow(BMessage* message)
75 	: BWindow(BRect(0, 0, 525, 430), NULL, B_DOCUMENT_WINDOW,
76 		B_AUTO_UPDATE_SIZE_LIMITS),
77 	fSearchText(NULL),
78 	fSearchResults(NULL),
79 	fMenuBar(NULL),
80 	fFileMenu(NULL),
81 	fNew(NULL),
82 	fOpen(NULL),
83 	fClose(NULL),
84 	fQuit(NULL),
85 	fActionMenu(NULL),
86 	fSelectAll(NULL),
87 	fSearch(NULL),
88 	fTrimSelection(NULL),
89 	fCopyText(NULL),
90 	fSelectInTracker(NULL),
91 	fOpenSelection(NULL),
92 	fPreferencesMenu(NULL),
93 	fRecurseLinks(NULL),
94 	fRecurseDirs(NULL),
95 	fSkipDotDirs(NULL),
96 	fCaseSensitive(NULL),
97 	fRegularExpression(NULL),
98 	fTextOnly(NULL),
99 	fInvokeEditor(NULL),
100 	fHistoryMenu(NULL),
101 	fEncodingMenu(NULL),
102 	fUTF8(NULL),
103 	fShiftJIS(NULL),
104 	fEUC(NULL),
105 	fJIS(NULL),
106 
107 	fShowLinesCheckbox(NULL),
108 	fButton(NULL),
109 
110 	fGrepper(NULL),
111 	fOldPattern(""),
112 	fModel(new (nothrow) Model()),
113 	fLastNodeMonitorEvent(system_time()),
114 	fChangesIterator(NULL),
115 	fChangesPulse(NULL),
116 
117 	fFilePanel(NULL)
118 {
119 	if (fModel == NULL)
120 		return;
121 
122 	entry_ref directory;
123 	_InitRefsReceived(&directory, message);
124 
125 	fModel->fDirectory = directory;
126 	fModel->fSelectedFiles = *message;
127 
128 	_SetWindowTitle();
129 	_CreateMenus();
130 	_CreateViews();
131 	_LayoutViews();
132 	_LoadPrefs();
133 	_TileIfMultipleWindows();
134 
135 	Show();
136 }
137 
138 
139 GrepWindow::~GrepWindow()
140 {
141 	delete fGrepper;
142 	delete fModel;
143 }
144 
145 
146 void GrepWindow::FrameResized(float width, float height)
147 {
148 	BWindow::FrameResized(width, height);
149 	fModel->fFrame = Frame();
150 	_SavePrefs();
151 }
152 
153 
154 void GrepWindow::FrameMoved(BPoint origin)
155 {
156 	BWindow::FrameMoved(origin);
157 	fModel->fFrame = Frame();
158 	_SavePrefs();
159 }
160 
161 
162 void GrepWindow::MenusBeginning()
163 {
164 	fModel->FillHistoryMenu(fHistoryMenu);
165 	BWindow::MenusBeginning();
166 }
167 
168 
169 void GrepWindow::MenusEnded()
170 {
171 	for (int32 t = fHistoryMenu->CountItems(); t > 0; --t)
172 		delete fHistoryMenu->RemoveItem(t - 1);
173 
174 	BWindow::MenusEnded();
175 }
176 
177 
178 void GrepWindow::MessageReceived(BMessage* message)
179 {
180 	switch (message->what) {
181 		case MSG_NEW_WINDOW:
182 			_OnNewWindow();
183 			break;
184 
185 		case B_SIMPLE_DATA:
186 			_OnFileDrop(message);
187 			break;
188 
189 		case MSG_OPEN_PANEL:
190 			_OnOpenPanel();
191 			break;
192 
193 		case MSG_REFS_RECEIVED:
194 			_OnRefsReceived(message);
195 			break;
196 
197 		case B_CANCEL:
198 			_OnOpenPanelCancel();
199 			break;
200 
201 		case MSG_RECURSE_LINKS:
202 			_OnRecurseLinks();
203 			break;
204 
205 		case MSG_RECURSE_DIRS:
206 			_OnRecurseDirs();
207 			break;
208 
209 		case MSG_SKIP_DOT_DIRS:
210 			_OnSkipDotDirs();
211 			break;
212 
213 		case MSG_CASE_SENSITIVE:
214 			_OnCaseSensitive();
215 			break;
216 
217 		case MSG_REGULAR_EXPRESSION:
218 			_OnRegularExpression();
219 			break;
220 
221 		case MSG_TEXT_ONLY:
222 			_OnTextOnly();
223 			break;
224 
225 		case MSG_INVOKE_EDITOR:
226 			_OnInvokeEditor();
227 			break;
228 
229 		case MSG_SEARCH_TEXT:
230 			_OnSearchText();
231 			break;
232 
233 		case MSG_SELECT_HISTORY:
234 			_OnHistoryItem(message);
235 			break;
236 
237 		case MSG_START_CANCEL:
238 			_OnStartCancel();
239 			break;
240 
241 		case MSG_SEARCH_FINISHED:
242 			_OnSearchFinished();
243 			break;
244 
245 		case MSG_START_NODE_MONITORING:
246 			_StartNodeMonitoring();
247 			break;
248 
249 		case B_PATH_MONITOR:
250 			_OnNodeMonitorEvent(message);
251 			break;
252 
253 		case MSG_NODE_MONITOR_PULSE:
254 			_OnNodeMonitorPulse();
255 			break;
256 
257 		case MSG_REPORT_FILE_NAME:
258 			_OnReportFileName(message);
259 			break;
260 
261 		case MSG_REPORT_RESULT:
262 			_OnReportResult(message);
263 			break;
264 
265 		case MSG_REPORT_ERROR:
266 			_OnReportError(message);
267 			break;
268 
269 		case MSG_SELECT_ALL:
270 			_OnSelectAll(message);
271 			break;
272 
273 		case MSG_TRIM_SELECTION:
274 			_OnTrimSelection();
275 			break;
276 
277 		case MSG_COPY_TEXT:
278 			_OnCopyText();
279 			break;
280 
281 		case MSG_SELECT_IN_TRACKER:
282 			_OnSelectInTracker();
283 			break;
284 
285 		case MSG_CHECKBOX_SHOW_LINES:
286 			_OnCheckboxShowLines();
287 			break;
288 
289 		case MSG_OPEN_SELECTION:
290 			// fall through
291 		case MSG_INVOKE_ITEM:
292 			_OnInvokeItem();
293 			break;
294 
295 		case MSG_QUIT_NOW:
296 			_OnQuitNow();
297 			break;
298 
299 		case 'utf8':
300 			fModel->fEncoding = 0;
301 			break;
302 
303 		case B_SJIS_CONVERSION:
304 			fModel->fEncoding = B_SJIS_CONVERSION;
305 			break;
306 
307 		case B_EUC_CONVERSION:
308 			fModel->fEncoding = B_EUC_CONVERSION;
309 			break;
310 
311 		case B_JIS_CONVERSION:
312 			fModel->fEncoding = B_JIS_CONVERSION;
313 			break;
314 
315 		default:
316 			BWindow::MessageReceived(message);
317 			break;
318 	}
319 }
320 
321 
322 void
323 GrepWindow::Quit()
324 {
325 	CALLED();
326 
327 	_StopNodeMonitoring();
328 	_SavePrefs();
329 
330 	// TODO: stippi: Looks like this could be done
331 	// by maintaining a counter in GrepApp with the number of open
332 	// grep windows... and just quit when it goes zero
333 	if (be_app->Lock()) {
334 		be_app->PostMessage(MSG_TRY_QUIT);
335 		be_app->Unlock();
336 		BWindow::Quit();
337 	}
338 }
339 
340 
341 // #pragma mark -
342 
343 
344 void
345 GrepWindow::_InitRefsReceived(entry_ref* directory, BMessage* message)
346 {
347 	// HACK-HACK-HACK:
348 	// If the user selected a single folder and invoked TextSearch on it,
349 	// but recurse directories is switched off, TextSearch would do nothing.
350 	// In that special case, we'd like it to recurse into that folder (but
351 	// not go any deeper after that).
352 
353 	type_code code;
354 	int32 count;
355 	message->GetInfo("refs", &code, &count);
356 
357 	if (count == 0) {
358 		if (message->FindRef("dir_ref", 0, directory) == B_OK)
359 			message->MakeEmpty();
360 	}
361 
362 	if (count == 1) {
363 		entry_ref ref;
364 		if (message->FindRef("refs", 0, &ref) == B_OK) {
365 			BEntry entry(&ref, true);
366 			if (entry.IsDirectory()) {
367 				// ok, special case, we use this folder as base directory
368 				// and pretend nothing had been selected:
369 				*directory = ref;
370 				message->MakeEmpty();
371 			}
372 		}
373 	}
374 }
375 
376 
377 void
378 GrepWindow::_SetWindowTitle()
379 {
380 	BEntry entry(&fModel->fDirectory, true);
381 	BString title;
382 	if (entry.InitCheck() == B_OK) {
383 		BPath path;
384 		if (entry.GetPath(&path) == B_OK) {
385 			if (fOldPattern.Length()) {
386 				title = B_TRANSLATE("%appname% : %path% : %searchtext%");
387 				title.ReplaceAll("%searchtext%", fOldPattern.String());
388 			} else
389 				title = B_TRANSLATE("%appname% : %path%");
390 
391 			title.ReplaceAll("%appname%", B_TRANSLATE_NOCOLLECT(kAppName));
392 			title.ReplaceAll("%path%", path.Path());
393 		}
394 	}
395 
396 	if (!title.Length())
397 		title = B_TRANSLATE_NOCOLLECT(kAppName);
398 
399 	SetTitle(title.String());
400 }
401 
402 
403 void
404 GrepWindow::_CreateMenus()
405 {
406 	fMenuBar = new BMenuBar("menubar");
407 
408 	fFileMenu = new BMenu(B_TRANSLATE("File"));
409 	fActionMenu = new BMenu(B_TRANSLATE("Actions"));
410 	fPreferencesMenu = new BMenu(B_TRANSLATE("Settings"));
411 	fHistoryMenu = new BMenu(B_TRANSLATE("History"));
412 	fEncodingMenu = new BMenu(B_TRANSLATE("Encoding"));
413 
414 	fNew = new BMenuItem(
415 		B_TRANSLATE("New window"), new BMessage(MSG_NEW_WINDOW), 'N');
416 
417 	fOpen = new BMenuItem(
418 		B_TRANSLATE("Set target" B_UTF8_ELLIPSIS), new BMessage(MSG_OPEN_PANEL), 'F');
419 
420 	fClose = new BMenuItem(
421 		B_TRANSLATE("Close"), new BMessage(B_QUIT_REQUESTED), 'W');
422 
423 	fQuit = new BMenuItem(
424 		B_TRANSLATE("Quit"), new BMessage(MSG_QUIT_NOW), 'Q');
425 
426 	fSearch = new BMenuItem(
427 		B_TRANSLATE("Search"), new BMessage(MSG_START_CANCEL), 'S');
428 
429 	fSelectAll = new BMenuItem(
430 		B_TRANSLATE("Select all"), new BMessage(MSG_SELECT_ALL), 'A');
431 
432 	fTrimSelection = new BMenuItem(
433 		B_TRANSLATE("Trim to selection"), new BMessage(MSG_TRIM_SELECTION), 'T');
434 
435 	fOpenSelection = new BMenuItem(
436 		B_TRANSLATE("Open selection"), new BMessage(MSG_OPEN_SELECTION), 'O');
437 
438 	fSelectInTracker = new BMenuItem(
439 		B_TRANSLATE("Show files in Tracker"),
440 			new BMessage(MSG_SELECT_IN_TRACKER), 'K');
441 
442 	fCopyText = new BMenuItem(
443 		B_TRANSLATE("Copy text to clipboard"), new BMessage(MSG_COPY_TEXT), 'B');
444 
445 	fRecurseLinks = new BMenuItem(
446 		B_TRANSLATE("Follow symbolic links"), new BMessage(MSG_RECURSE_LINKS));
447 
448 	fRecurseDirs = new BMenuItem(
449 		B_TRANSLATE("Look in sub-folders"), new BMessage(MSG_RECURSE_DIRS));
450 
451 	fSkipDotDirs = new BMenuItem(
452 		B_TRANSLATE("Skip folders starting with a dot"),
453 			new BMessage(MSG_SKIP_DOT_DIRS));
454 
455 	fCaseSensitive = new BMenuItem(
456 		B_TRANSLATE("Case-sensitive"), new BMessage(MSG_CASE_SENSITIVE));
457 
458 	fRegularExpression = new BMenuItem(
459 		B_TRANSLATE("Regular expression"), new BMessage(MSG_REGULAR_EXPRESSION));
460 
461 	fTextOnly = new BMenuItem(
462 		B_TRANSLATE("Text files only"), new BMessage(MSG_TEXT_ONLY));
463 
464 	fInvokeEditor = new BMenuItem(
465 		B_TRANSLATE("Open files in code editor"), new BMessage(MSG_INVOKE_EDITOR));
466 
467 	fUTF8 = new BMenuItem("UTF8", new BMessage('utf8'));
468 	fShiftJIS = new BMenuItem("ShiftJIS", new BMessage(B_SJIS_CONVERSION));
469 	fEUC = new BMenuItem("EUC", new BMessage(B_EUC_CONVERSION));
470 	fJIS = new BMenuItem("JIS", new BMessage(B_JIS_CONVERSION));
471 
472 	fFileMenu->AddItem(fNew);
473 	fFileMenu->AddSeparatorItem();
474 	fFileMenu->AddItem(fOpen);
475 	fFileMenu->AddItem(fClose);
476 	fFileMenu->AddSeparatorItem();
477 	fFileMenu->AddItem(fQuit);
478 
479 	fActionMenu->AddItem(fSearch);
480 	fActionMenu->AddSeparatorItem();
481 	fActionMenu->AddItem(fSelectAll);
482 	fActionMenu->AddItem(fTrimSelection);
483 	fActionMenu->AddSeparatorItem();
484 	fActionMenu->AddItem(fOpenSelection);
485 	fActionMenu->AddItem(fSelectInTracker);
486 	fActionMenu->AddItem(fCopyText);
487 
488 	fPreferencesMenu->AddItem(fRecurseLinks);
489 	fPreferencesMenu->AddItem(fRecurseDirs);
490 	fPreferencesMenu->AddItem(fSkipDotDirs);
491 	fPreferencesMenu->AddItem(fCaseSensitive);
492 	fPreferencesMenu->AddItem(fRegularExpression);
493 	fPreferencesMenu->AddItem(fTextOnly);
494 	fPreferencesMenu->AddItem(fInvokeEditor);
495 
496 	fEncodingMenu->AddItem(fUTF8);
497 	fEncodingMenu->AddItem(fShiftJIS);
498 	fEncodingMenu->AddItem(fEUC);
499 	fEncodingMenu->AddItem(fJIS);
500 
501 //	fEncodingMenu->SetLabelFromMarked(true);
502 		// Do we really want this ?
503 	fEncodingMenu->SetRadioMode(true);
504 	fEncodingMenu->ItemAt(0)->SetMarked(true);
505 
506 	fMenuBar->AddItem(fFileMenu);
507 	fMenuBar->AddItem(fActionMenu);
508 	fMenuBar->AddItem(fPreferencesMenu);
509 	fMenuBar->AddItem(fHistoryMenu);
510 	fMenuBar->AddItem(fEncodingMenu);
511 
512 	fSearch->SetEnabled(false);
513 }
514 
515 
516 void
517 GrepWindow::_CreateViews()
518 {
519 	// The search pattern entry field does not send a message when
520 	// <Enter> is pressed, because the "Search/Cancel" button already
521 	// does this and we don't want to send the same message twice.
522 
523 	fSearchText = new BTextControl(
524 		"SearchText", NULL, NULL, NULL,
525 		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_NAVIGABLE);
526 
527 	fSearchText->TextView()->SetMaxBytes(1000);
528 	fSearchText->SetModificationMessage(new BMessage(MSG_SEARCH_TEXT));
529 
530 	fButton = new BButton(
531 		"Button", B_TRANSLATE("Search"),
532 		new BMessage(MSG_START_CANCEL));
533 	fButton->MakeDefault(true);
534 	fButton->SetEnabled(false);
535 
536 	fShowLinesCheckbox = new BCheckBox(
537 		"ShowLines", B_TRANSLATE("Show lines"),
538 		new BMessage(MSG_CHECKBOX_SHOW_LINES));
539 	fShowLinesCheckbox->SetValue(B_CONTROL_ON);
540 
541 	fSearchResults = new GrepListView();
542 	fSearchResults->SetInvocationMessage(new BMessage(MSG_INVOKE_ITEM));
543 }
544 
545 
546 void
547 GrepWindow::_LayoutViews()
548 {
549 	BScrollView* scroller = new BScrollView(
550 		"ScrollSearchResults", fSearchResults,
551 		B_FULL_UPDATE_ON_RESIZE, true, true);
552 
553 	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
554 		.SetInsets(0, 0, -1, -1)
555 		.Add(fMenuBar)
556 		.AddGrid(B_USE_HALF_ITEM_INSETS)
557 			.SetInsets(B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING,
558 				B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING)
559 			.Add(fSearchText, 0, 0, 3)
560 			.Add(fShowLinesCheckbox, 0, 1)
561 			.Add(BSpaceLayoutItem::CreateGlue(), 1, 1)
562 			.Add(fButton, 2, 1)
563 		.End()
564 		.AddGroup(B_VERTICAL, 0)
565 			.SetInsets(-2, 0, -1, -1)
566 			.Add(scroller)
567 		.End()
568 	.End();
569 
570 	fSearchText->MakeFocus(true);
571 
572 	SetKeyMenuBar(fMenuBar);
573 }
574 
575 
576 void
577 GrepWindow::_TileIfMultipleWindows()
578 {
579 	if (be_app->Lock()) {
580 		int32 windowCount = be_app->CountWindows();
581 		be_app->Unlock();
582 
583 		if (windowCount > 1)
584 			MoveBy(20, 20);
585 	}
586 
587 	BScreen screen(this);
588 	BRect screenFrame = screen.Frame();
589 	BRect windowFrame = Frame();
590 
591 	if (windowFrame.left > screenFrame.right
592 		|| windowFrame.top > screenFrame.bottom
593 		|| windowFrame.right < screenFrame.left
594 		|| windowFrame.bottom < screenFrame.top)
595 		MoveTo(50, 50);
596 }
597 
598 
599 // #pragma mark -
600 
601 
602 void
603 GrepWindow::_LoadPrefs()
604 {
605 	Lock();
606 
607 	fModel->LoadPrefs();
608 
609 	fRecurseDirs->SetMarked(fModel->fRecurseDirs);
610 	fRecurseLinks->SetMarked(fModel->fRecurseLinks);
611 	fSkipDotDirs->SetMarked(fModel->fSkipDotDirs);
612 	fCaseSensitive->SetMarked(fModel->fCaseSensitive);
613 	fRegularExpression->SetMarked(fModel->fRegularExpression);
614 	fTextOnly->SetMarked(fModel->fTextOnly);
615 	fInvokeEditor->SetMarked(fModel->fInvokeEditor);
616 
617 	fShowLinesCheckbox->SetValue(fModel->fShowLines);
618 
619 	switch (fModel->fEncoding) {
620 		case 0:
621 			fUTF8->SetMarked(true);
622 			break;
623 		case B_SJIS_CONVERSION:
624 			fShiftJIS->SetMarked(true);
625 			break;
626 		case B_EUC_CONVERSION:
627 			fEUC->SetMarked(true);
628 			break;
629 		case B_JIS_CONVERSION:
630 			fJIS->SetMarked(true);
631 			break;
632 		default:
633 			printf("Woops. Bad fModel->fEncoding value.\n");
634 			break;
635 	}
636 
637 	MoveTo(fModel->fFrame.left, fModel->fFrame.top);
638 	ResizeTo(fModel->fFrame.Width(), fModel->fFrame.Height());
639 
640 	Unlock();
641 }
642 
643 
644 void
645 GrepWindow::_SavePrefs()
646 {
647 	fModel->SavePrefs();
648 }
649 
650 
651 void
652 GrepWindow::_StartNodeMonitoring()
653 {
654 	CALLED();
655 
656 	_StopNodeMonitoring();
657 
658 	BMessenger messenger(this);
659 	uint32 fileFlags = B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR;
660 
661 
662 	// watch the top level folder only, rest should be done through filtering
663 	// the node monitor notifications
664 	BPath path(&fModel->fDirectory);
665 	if (path.InitCheck() == B_OK) {
666 		TRACE_NM("start monitoring root folder: %s\n", path.Path());
667 		BPrivate::BPathMonitor::StartWatching(path.Path(),
668 			fileFlags | B_WATCH_RECURSIVELY | B_WATCH_FILES_ONLY, messenger);
669 	}
670 
671 	if (fChangesPulse == NULL) {
672 		BMessage message(MSG_NODE_MONITOR_PULSE);
673 		fChangesPulse = new BMessageRunner(BMessenger(this), &message,
674 			kChangesPulseInterval);
675 	}
676 }
677 
678 
679 void
680 GrepWindow::_StopNodeMonitoring()
681 {
682 	if (fChangesPulse == NULL)
683 		return;
684 
685 	CALLED();
686 
687 	BPrivate::BPathMonitor::StopWatching(BMessenger(this));
688 	delete fChangesIterator;
689 	fChangesIterator = NULL;
690 	delete fChangesPulse;
691 	fChangesPulse = NULL;
692 }
693 
694 
695 // #pragma mark - events
696 
697 
698 void
699 GrepWindow::_OnStartCancel()
700 {
701 	CALLED();
702 
703 	_StopNodeMonitoring();
704 
705 	if (fModel->fState == STATE_IDLE) {
706 		fSearchResults->MakeEmpty();
707 
708 		if (fSearchText->TextView()->TextLength() == 0)
709 			return;
710 
711 		fModel->fState = STATE_SEARCH;
712 
713 		fModel->AddToHistory(fSearchText->Text());
714 
715 		// From now on, we don't want to be notified when the
716 		// search pattern changes, because the control will be
717 		// displaying the names of the files we are grepping.
718 
719 		fSearchText->SetModificationMessage(NULL);
720 
721 		fFileMenu->SetEnabled(false);
722 		fActionMenu->SetEnabled(false);
723 		fPreferencesMenu->SetEnabled(false);
724 		fHistoryMenu->SetEnabled(false);
725 		fEncodingMenu->SetEnabled(false);
726 
727 		fSearchText->SetEnabled(false);
728 
729 		fButton->MakeFocus(true);
730 		fButton->SetLabel(B_TRANSLATE("Cancel"));
731 
732 		fSearch->SetEnabled(false);
733 
734 		// We need to remember the search pattern, because during
735 		// the grepping, the text control's text will be replaced
736 		// by the name of the file that's currently being grepped.
737 		// When the grepping finishes, we need to restore the old
738 		// search pattern.
739 
740 		fOldPattern = fSearchText->Text();
741 
742 		_SetWindowTitle();
743 
744 		FileIterator* iterator = new (nothrow) InitialIterator(fModel);
745 		fGrepper = new (nothrow) Grepper(fOldPattern.String(), fModel,
746 			this, iterator);
747 		if (fGrepper != NULL && fGrepper->IsValid())
748 			fGrepper->Start();
749 		else {
750 			// roll back in case of problems
751 			if (fGrepper == NULL)
752 				delete iterator;
753 			else {
754 				// Grepper owns iterator
755 				delete fGrepper;
756 				fGrepper = NULL;
757 			}
758 			fModel->fState = STATE_IDLE;
759 			// TODO: better notification to user
760 			fprintf(stderr, "Out of memory.\n");
761 		}
762 	} else if (fModel->fState == STATE_SEARCH) {
763 		fModel->fState = STATE_CANCEL;
764 		fGrepper->Cancel();
765 	}
766 }
767 
768 
769 void
770 GrepWindow::_OnSearchFinished()
771 {
772 	fModel->fState = STATE_IDLE;
773 
774 	delete fGrepper;
775 	fGrepper = NULL;
776 
777 	fFileMenu->SetEnabled(true);
778 	fActionMenu->SetEnabled(true);
779 	fPreferencesMenu->SetEnabled(true);
780 	fHistoryMenu->SetEnabled(true);
781 	fEncodingMenu->SetEnabled(true);
782 
783 	fButton->SetLabel(B_TRANSLATE("Search"));
784 
785 	fButton->SetEnabled(true);
786 	fSearch->SetEnabled(true);
787 
788 	fSearchText->SetEnabled(true);
789 	fSearchText->MakeFocus(true);
790 	fSearchText->SetText(fOldPattern.String());
791 	fSearchText->TextView()->SelectAll();
792 	fSearchText->SetModificationMessage(new BMessage(MSG_SEARCH_TEXT));
793 
794 	PostMessage(MSG_START_NODE_MONITORING);
795 }
796 
797 
798 void
799 GrepWindow::_OnNodeMonitorEvent(BMessage* message)
800 {
801 	int32 opCode;
802 	if (message->FindInt32("opcode", &opCode) != B_OK)
803 		return;
804 
805 	if (fChangesIterator == NULL) {
806 		fChangesIterator = new (nothrow) ChangesIterator(fModel);
807 		if (fChangesIterator == NULL || !fChangesIterator->IsValid()) {
808 			delete fChangesIterator;
809 			fChangesIterator = NULL;
810 		}
811 	}
812 
813 	switch (opCode) {
814 		case B_ENTRY_CREATED:
815 		case B_ENTRY_REMOVED:
816 		{
817 			TRACE_NM("%s\n", opCode == B_ENTRY_CREATED ? "B_ENTRY_CREATED"
818 				: "B_ENTRY_REMOVED");
819 			BString path;
820 			if (message->FindString("path", &path) == B_OK) {
821 				if (opCode == B_ENTRY_CREATED) {
822 					if (fChangesIterator != NULL)
823 						fChangesIterator->EntryAdded(path.String());
824 				} else {
825 					// in order to remove temporary files
826 					if (fChangesIterator != NULL)
827 						fChangesIterator->EntryRemoved(path.String());
828 					// remove from the list view already
829 					BEntry entry(path.String());
830 					entry_ref ref;
831 					if (entry.GetRef(&ref) == B_OK)
832 						fSearchResults->RemoveResults(ref, true);
833 				}
834 			} else {
835 				#ifdef TRACE_NODE_MONITORING
836 					printf("incompatible message:\n");
837 					message->PrintToStream();
838 				#endif
839 			}
840 			TRACE_NM("path: %s\n", path.String());
841 			break;
842 		}
843 		case B_ENTRY_MOVED:
844 		{
845 			TRACE_NM("B_ENTRY_MOVED\n");
846 
847 			BString path;
848 			if (message->FindString("path", &path) != B_OK) {
849 				#ifdef TRACE_NODE_MONITORING
850 					printf("incompatible message:\n");
851 					message->PrintToStream();
852 				#endif
853 				break;
854 			}
855 
856 			bool added;
857 			if (message->FindBool("added", &added) != B_OK)
858 				added = false;
859 			bool removed;
860 			if (message->FindBool("removed", &removed) != B_OK)
861 				removed = false;
862 
863 			if (added) {
864 				// new files
865 			} else if (removed) {
866 				// remove files
867 			} else {
868 				// files changed location, but are still within the search
869 				// path!
870 				BEntry entry(path.String());
871 				entry_ref ref;
872 				if (entry.GetRef(&ref) == B_OK) {
873 					int32 index;
874 					ResultItem* item = fSearchResults->FindItem(ref, &index);
875 					if (item != NULL) {
876 						item->SetText(path.String());
877 						// take care of invalidation, the index is currently
878 						// the full list index, but needs to be the visible
879 						// items index for this
880 						index = fSearchResults->IndexOf(item);
881 						fSearchResults->InvalidateItem(index);
882 					}
883 				}
884 			}
885 			break;
886 		}
887 		case B_STAT_CHANGED:
888 		case B_ATTR_CHANGED:
889 		{
890 			TRACE_NM("%s\n", opCode == B_STAT_CHANGED ? "B_STAT_CHANGED"
891 				: "B_ATTR_CHANGED");
892 			// For directly watched files, the path will include the
893 			// name. When the event occurs for a file in a watched directory,
894 			// the message will have an extra name field for the respective
895 			// file.
896 			BString path;
897 			if (message->FindString("path", &path) == B_OK) {
898 				if (fChangesIterator != NULL)
899 					fChangesIterator->EntryChanged(path.String());
900 			} else {
901 				#ifdef TRACE_NODE_MONITORING
902 					printf("incompatible message:\n");
903 					message->PrintToStream();
904 				#endif
905 			}
906 			TRACE_NM("path: %s\n", path.String());
907 // message->PrintToStream();
908 			break;
909 		}
910 
911 		default:
912 			TRACE_NM("unkown op code\n");
913 			break;
914 	}
915 
916 	fLastNodeMonitorEvent = system_time();
917 }
918 
919 
920 void
921 GrepWindow::_OnNodeMonitorPulse()
922 {
923 	if (fChangesIterator == NULL || fChangesIterator->IsEmpty())
924 		return;
925 
926 	if (system_time() - fLastNodeMonitorEvent < kChangesPulseInterval) {
927 		// wait for things to settle down before running the search for changes
928 		return;
929 	}
930 
931 	if (fModel->fState != STATE_IDLE) {
932 		// An update or search is still in progress. New node monitor messages
933 		// may arrive while an update is still running. They should not arrive
934 		// during a regular search, but we want to be prepared for that anyways
935 		// and check != STATE_IDLE.
936 		return;
937 	}
938 
939 	fOldPattern = fSearchText->Text();
940 
941 #ifdef TRACE_NODE_MONITORING
942 	fChangesIterator->PrintToStream();
943 #endif
944 
945 	fGrepper = new (nothrow) Grepper(fOldPattern.String(), fModel,
946 		this, fChangesIterator);
947 	if (fGrepper != NULL && fGrepper->IsValid()) {
948 		fGrepper->Start();
949 		fChangesIterator = NULL;
950 		fModel->fState = STATE_UPDATE;
951 	} else {
952 		// roll back in case of problems
953 		if (fGrepper == NULL)
954 			delete fChangesIterator;
955 		else {
956 			// Grepper owns iterator
957 			delete fGrepper;
958 			fGrepper = NULL;
959 		}
960 		fprintf(stderr, "Out of memory.\n");
961 	}
962 }
963 
964 
965 void
966 GrepWindow::_OnReportFileName(BMessage* message)
967 {
968 	if (fModel->fState != STATE_UPDATE) {
969 		BString name = message->FindString("filename");
970 		fSearchText->TruncateString(&name, B_TRUNCATE_MIDDLE,
971 			fSearchText->Bounds().Width() - 10);
972 
973 		fSearchText->SetText(name);
974 	}
975 }
976 
977 
978 void
979 GrepWindow::_OnReportResult(BMessage* message)
980 {
981 	CALLED();
982 	entry_ref ref;
983 	if (message->FindRef("ref", &ref) != B_OK)
984 		return;
985 
986 	type_code type;
987 	int32 count;
988 	message->GetInfo("text", &type, &count);
989 
990 	BStringItem* item = NULL;
991 	if (fModel->fState == STATE_UPDATE) {
992 		// During updates because of node monitor events, negatives are
993 		// also reported (count == 0).
994 		item = fSearchResults->RemoveResults(ref, count == 0);
995 	}
996 
997 	if (count == 0)
998 		return;
999 
1000 	if (item == NULL) {
1001 		item = new ResultItem(ref);
1002 		fSearchResults->AddItem(item);
1003 		item->SetExpanded(fShowLinesCheckbox->Value() == 1);
1004 	}
1005 
1006 	const char* buf;
1007 	while (message->FindString("text", --count, &buf) == B_OK) {
1008 		uchar* temp = (uchar*)strdup(buf);
1009 		uchar* ptr = temp;
1010 
1011 		while (true) {
1012 			// replace all non-printable characters by spaces
1013 			uchar c = *ptr;
1014 
1015 			if (c == '\0')
1016 				break;
1017 
1018 			if (!(c & 0x80) && iscntrl(c))
1019 				*ptr = ' ';
1020 
1021 			++ptr;
1022 		}
1023 
1024 		fSearchResults->AddUnder(new BStringItem((const char*)temp), item);
1025 
1026 		free(temp);
1027 	}
1028 }
1029 
1030 
1031 void
1032 GrepWindow::_OnReportError(BMessage* message)
1033 {
1034 	const char* buf;
1035 	if (message->FindString("error", &buf) == B_OK)
1036 		fSearchResults->AddItem(new BStringItem(buf));
1037 }
1038 
1039 
1040 void
1041 GrepWindow::_OnRecurseLinks()
1042 {
1043 	fModel->fRecurseLinks = !fModel->fRecurseLinks;
1044 	fRecurseLinks->SetMarked(fModel->fRecurseLinks);
1045 	_ModelChanged();
1046 }
1047 
1048 
1049 void
1050 GrepWindow::_OnRecurseDirs()
1051 {
1052 	fModel->fRecurseDirs = !fModel->fRecurseDirs;
1053 	fRecurseDirs->SetMarked(fModel->fRecurseDirs);
1054 	_ModelChanged();
1055 }
1056 
1057 
1058 void
1059 GrepWindow::_OnSkipDotDirs()
1060 {
1061 	fModel->fSkipDotDirs = !fModel->fSkipDotDirs;
1062 	fSkipDotDirs->SetMarked(fModel->fSkipDotDirs);
1063 	_ModelChanged();
1064 }
1065 
1066 
1067 void
1068 GrepWindow::_OnRegularExpression()
1069 {
1070 	fModel->fRegularExpression = !fModel->fRegularExpression;
1071 	fRegularExpression->SetMarked(fModel->fRegularExpression);
1072 	_ModelChanged();
1073 }
1074 
1075 
1076 void
1077 GrepWindow::_OnCaseSensitive()
1078 {
1079 	fModel->fCaseSensitive = !fModel->fCaseSensitive;
1080 	fCaseSensitive->SetMarked(fModel->fCaseSensitive);
1081 	_ModelChanged();
1082 }
1083 
1084 
1085 void
1086 GrepWindow::_OnTextOnly()
1087 {
1088 	fModel->fTextOnly = !fModel->fTextOnly;
1089 	fTextOnly->SetMarked(fModel->fTextOnly);
1090 	_ModelChanged();
1091 }
1092 
1093 
1094 void
1095 GrepWindow::_OnInvokeEditor()
1096 {
1097 	fModel->fInvokeEditor = !fModel->fInvokeEditor;
1098 	fInvokeEditor->SetMarked(fModel->fInvokeEditor);
1099 	_SavePrefs();
1100 }
1101 
1102 
1103 void
1104 GrepWindow::_OnCheckboxShowLines()
1105 {
1106 	// Selection in BOutlineListView in multiple selection mode
1107 	// gets weird when collapsing. I've tried all sorts of things.
1108 	// It seems impossible to make it behave just right.
1109 
1110 	// Going from collapsed to expande mode, the superitems
1111 	// keep their selection, the subitems don't (yet) have
1112 	// a selection. This works as expected, AFAIK.
1113 
1114 	// Going from expanded to collapsed mode, I would like
1115 	// for a selected subitem (line) to select its superitem,
1116 	// (its file) and the subitem be unselected.
1117 
1118 	// I've successfully tried code patches that apply the
1119 	// selection pattern that I want, but with weird effects
1120 	// on subsequent manual selection.
1121 	// Lines stay selected while the user tries to select
1122 	// some other line. It just gets weird.
1123 
1124 	// It's as though listItem->Select() and Deselect()
1125 	// put the items in some semi-selected state.
1126 	// Or maybe I've got it all wrong.
1127 
1128 	// So, here's the plain basic collapse/expand.
1129 	// I think it's the least bad of what's possible on BeOS R5,
1130 	// but perhaps someone comes along with a patch of magic.
1131 
1132 	fModel->fShowLines = (fShowLinesCheckbox->Value() == 1);
1133 
1134 	int32 numItems = fSearchResults->FullListCountItems();
1135 	for (int32 x = 0; x < numItems; ++x) {
1136 		BListItem* listItem = fSearchResults->FullListItemAt(x);
1137 		if (listItem->OutlineLevel() == 0) {
1138 			if (fModel->fShowLines) {
1139 				if (!fSearchResults->IsExpanded(x))
1140 					fSearchResults->Expand(listItem);
1141 			} else {
1142 				if (fSearchResults->IsExpanded(x))
1143 					fSearchResults->Collapse(listItem);
1144 			}
1145 		}
1146 	}
1147 
1148 	fSearchResults->Invalidate();
1149 
1150 	_SavePrefs();
1151 }
1152 
1153 
1154 void
1155 GrepWindow::_OnInvokeItem()
1156 {
1157 	for (int32 selectionIndex = 0; ; selectionIndex++) {
1158 		int32 itemIndex = fSearchResults->CurrentSelection(selectionIndex);
1159 		BListItem* item = fSearchResults->ItemAt(itemIndex);
1160 		if (item == NULL)
1161 			break;
1162 
1163 		int32 level = item->OutlineLevel();
1164 		int32 lineNum = -1;
1165 
1166 		// Get the line number.
1167 		// only this level has line numbers
1168 		if (level == 1) {
1169 			BStringItem* str = dynamic_cast<BStringItem*>(item);
1170 			if (str != NULL) {
1171 				lineNum = atol(str->Text());
1172 					// fortunately, atol knows when to stop the conversion
1173 			}
1174 		}
1175 
1176 		// Get the top-most item and launch its entry_ref.
1177 		while (level != 0) {
1178 			item = fSearchResults->Superitem(item);
1179 			if (item == NULL)
1180 				break;
1181 			level = item->OutlineLevel();
1182 		}
1183 
1184 		ResultItem* entry = dynamic_cast<ResultItem*>(item);
1185 		if (entry != NULL) {
1186 			if (fModel->fInvokeEditor && _OpenInEditor(entry->ref, lineNum))
1187 				return;
1188 
1189 			// ask tracker to open it for us
1190 			BMessenger target(TRACKER_SIGNATURE);
1191 			BMessage message(B_REFS_RECEIVED);
1192 			message.AddRef("refs", &entry->ref);
1193 			if (lineNum > -1) {
1194 				message.AddInt32("be:line", lineNum);
1195 			}
1196 			target.SendMessage(&message);
1197 		}
1198 	}
1199 }
1200 
1201 
1202 void
1203 GrepWindow::_OnSearchText()
1204 {
1205 	CALLED();
1206 
1207 	bool enabled = fSearchText->TextView()->TextLength() != 0;
1208 	fButton->SetEnabled(enabled);
1209 	fSearch->SetEnabled(enabled);
1210 	_StopNodeMonitoring();
1211 }
1212 
1213 
1214 void
1215 GrepWindow::_OnHistoryItem(BMessage* message)
1216 {
1217 	const char* buf;
1218 	if (message->FindString("text", &buf) == B_OK)
1219 		fSearchText->SetText(buf);
1220 }
1221 
1222 
1223 void
1224 GrepWindow::_OnTrimSelection()
1225 {
1226 	if (fSearchResults->CurrentSelection() < 0) {
1227 		BString text;
1228 		text << B_TRANSLATE("Please select the files you wish to keep searching.");
1229 		text << "\n";
1230 		text << B_TRANSLATE("The unselected files will be removed from the list.");
1231 		text << "\n";
1232 		BAlert* alert = new BAlert(NULL, text.String(), B_TRANSLATE("OK"), NULL, NULL,
1233 			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1234 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1235 		alert->Go(NULL);
1236 		return;
1237 	}
1238 
1239 	BMessage message;
1240 	BString path;
1241 
1242 	for (int32 index = 0; ; index++) {
1243 		BStringItem* item = dynamic_cast<BStringItem*>(
1244 			fSearchResults->ItemAt(index));
1245 		if (item == NULL)
1246 			break;
1247 
1248 		if (!item->IsSelected() || item->OutlineLevel() != 0)
1249 			continue;
1250 
1251 		if (path == item->Text())
1252 			continue;
1253 
1254 		path = item->Text();
1255 		entry_ref ref;
1256 		if (get_ref_for_path(path.String(), &ref) == B_OK)
1257 			message.AddRef("refs", &ref);
1258 	}
1259 
1260 	fModel->fDirectory = entry_ref();
1261 		// invalidated on purpose
1262 
1263 	fModel->fSelectedFiles.MakeEmpty();
1264 	fModel->fSelectedFiles = message;
1265 
1266 	PostMessage(MSG_START_CANCEL);
1267 
1268 	_SetWindowTitle();
1269 }
1270 
1271 
1272 void
1273 GrepWindow::_OnCopyText()
1274 {
1275 	bool onlyCopySelection = true;
1276 
1277 	if (fSearchResults->CurrentSelection() < 0)
1278 		onlyCopySelection = false;
1279 
1280 	BString buffer;
1281 
1282 	for (int32 index = 0; ; index++) {
1283 		BStringItem* item = dynamic_cast<BStringItem*>(
1284 			fSearchResults->ItemAt(index));
1285 		if (item == NULL)
1286 			break;
1287 
1288 		if (onlyCopySelection) {
1289 			if (item->IsSelected())
1290 				buffer << item->Text() << "\n";
1291 		} else
1292 			buffer << item->Text() << "\n";
1293 	}
1294 
1295 	status_t status = B_OK;
1296 
1297 	BMessage* clip = NULL;
1298 
1299 	if (be_clipboard->Lock()) {
1300 		be_clipboard->Clear();
1301 
1302 		clip = be_clipboard->Data();
1303 
1304 		clip->AddData("text/plain", B_MIME_TYPE, buffer.String(),
1305 			buffer.Length());
1306 
1307 		status = be_clipboard->Commit();
1308 
1309 		if (status != B_OK) {
1310 			be_clipboard->Unlock();
1311 			return;
1312 		}
1313 
1314 		be_clipboard->Unlock();
1315 	}
1316 }
1317 
1318 
1319 void
1320 GrepWindow::_OnSelectInTracker()
1321 {
1322 	if (fSearchResults->CurrentSelection() < 0) {
1323 		BAlert* alert = new BAlert("Info",
1324 			B_TRANSLATE("Please select the files you wish to have selected for you in "
1325 				"Tracker."),
1326 			B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1327 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1328 		alert->Go(NULL);
1329 		return;
1330 	}
1331 
1332 	BMessage message;
1333 	BString filePath;
1334 	BPath folderPath;
1335 	BList folderList;
1336 	BString lastFolderAddedToList;
1337 
1338 	for (int32 index = 0; ; index++) {
1339 		BStringItem* item = dynamic_cast<BStringItem*>(
1340 			fSearchResults->ItemAt(index));
1341 		if (item == NULL)
1342 			break;
1343 
1344 		// only open selected and top level (file) items
1345 		if (!item->IsSelected() || item->OutlineLevel() > 0)
1346 			continue;
1347 
1348 		// check if this was previously opened
1349 		if (filePath == item->Text())
1350 			continue;
1351 
1352 		filePath = item->Text();
1353 		entry_ref file_ref;
1354 		if (get_ref_for_path(filePath.String(), &file_ref) != B_OK)
1355 			continue;
1356 
1357 		message.AddRef("refs", &file_ref);
1358 
1359 		// add parent folder to list of folders to open
1360 		folderPath.SetTo(filePath.String());
1361 		if (folderPath.GetParent(&folderPath) == B_OK) {
1362 			BPath* path = new BPath(folderPath);
1363 			if (path->Path() != lastFolderAddedToList) {
1364 				// catches some duplicates
1365 				folderList.AddItem(path);
1366 				lastFolderAddedToList = path->Path();
1367 			} else
1368 				delete path;
1369 		}
1370 	}
1371 
1372 	_RemoveFolderListDuplicates(&folderList);
1373 	_OpenFoldersInTracker(&folderList);
1374 
1375 	int32 aShortWhile = 100000;
1376 	snooze(aShortWhile);
1377 
1378 	if (!_AreAllFoldersOpenInTracker(&folderList)) {
1379 		for (int32 x = 0; x < 5; x++) {
1380 			aShortWhile += 100000;
1381 			snooze(aShortWhile);
1382 			_OpenFoldersInTracker(&folderList);
1383 		}
1384 	}
1385 
1386 	if (!_AreAllFoldersOpenInTracker(&folderList)) {
1387 		BString str1;
1388 		str1 << B_TRANSLATE("%APP_NAME couldn't open one or more folders.");
1389 		str1.ReplaceFirst("%APP_NAME", B_TRANSLATE_NOCOLLECT(kAppName));
1390 		BAlert* alert = new BAlert(NULL, str1.String(), B_TRANSLATE("OK"),
1391 			NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
1392 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1393 		alert->Go(NULL);
1394 		goto out;
1395 	}
1396 
1397 	_SelectFilesInTracker(&folderList, &message);
1398 
1399 out:
1400 	// delete folderList contents
1401 	int32 folderCount = folderList.CountItems();
1402 	for (int32 x = 0; x < folderCount; x++)
1403 		delete static_cast<BPath*>(folderList.ItemAt(x));
1404 }
1405 
1406 
1407 void
1408 GrepWindow::_OnQuitNow()
1409 {
1410 	if (be_app->Lock()) {
1411 		be_app->PostMessage(B_QUIT_REQUESTED);
1412 		be_app->Unlock();
1413 	}
1414 }
1415 
1416 
1417 void
1418 GrepWindow::_OnFileDrop(BMessage* message)
1419 {
1420 	if (fModel->fState != STATE_IDLE)
1421 		return;
1422 
1423 	entry_ref directory;
1424 	_InitRefsReceived(&directory, message);
1425 
1426 	fModel->fDirectory = directory;
1427 	fModel->fSelectedFiles.MakeEmpty();
1428 	fModel->fSelectedFiles = *message;
1429 
1430 	fSearchResults->MakeEmpty();
1431 	fOldPattern = "";
1432 
1433 	_SetWindowTitle();
1434 }
1435 
1436 
1437 void
1438 GrepWindow::_OnRefsReceived(BMessage* message)
1439 {
1440 	_OnFileDrop(message);
1441 	fOldPattern = "";
1442 	// It seems a B_CANCEL always follows a B_REFS_RECEIVED
1443 	// from a BFilePanel in Open mode.
1444 	//
1445 	// _OnOpenPanelCancel() is called on B_CANCEL.
1446 	// That's where saving the current dir of the file panel occurs, for now,
1447 	// and also the neccesary deletion of the file panel object.
1448 	// A hidden file panel would otherwise jam the shutdown process.
1449 }
1450 
1451 
1452 void
1453 GrepWindow::_OnOpenPanel()
1454 {
1455 	if (fFilePanel != NULL)
1456 		return;
1457 
1458 	entry_ref path;
1459 	if (get_ref_for_path(fModel->fFilePanelPath.String(), &path) != B_OK)
1460 		return;
1461 
1462 	BMessenger messenger(this);
1463 	BMessage message(MSG_REFS_RECEIVED);
1464 	fFilePanel = new BFilePanel(B_OPEN_PANEL, &messenger, &path,
1465 		B_FILE_NODE | B_DIRECTORY_NODE | B_SYMLINK_NODE, true,
1466 		&message, NULL, true, true);
1467 
1468 	fFilePanel->Show();
1469 }
1470 
1471 
1472 void
1473 GrepWindow::_OnOpenPanelCancel()
1474 {
1475 	entry_ref panelDirRef;
1476 	fFilePanel->GetPanelDirectory(&panelDirRef);
1477 	BPath path(&panelDirRef);
1478 	fModel->fFilePanelPath = path.Path();
1479 	delete fFilePanel;
1480 	fFilePanel = NULL;
1481 }
1482 
1483 
1484 void
1485 GrepWindow::_OnSelectAll(BMessage* message)
1486 {
1487 	BMessenger messenger(fSearchResults);
1488 	messenger.SendMessage(B_SELECT_ALL);
1489 }
1490 
1491 
1492 void
1493 GrepWindow::_OnNewWindow()
1494 {
1495 	BMessage cloneRefs;
1496 		// we don't want GrepWindow::InitRefsReceived()
1497 		// to mess with the refs of the current window
1498 
1499 	cloneRefs = fModel->fSelectedFiles;
1500 	cloneRefs.AddRef("dir_ref", &(fModel->fDirectory));
1501 
1502 	new GrepWindow(&cloneRefs);
1503 }
1504 
1505 
1506 // #pragma mark -
1507 
1508 
1509 void
1510 GrepWindow::_ModelChanged()
1511 {
1512 	CALLED();
1513 
1514 	_StopNodeMonitoring();
1515 	_SavePrefs();
1516 }
1517 
1518 bool
1519 GrepWindow::_OpenInEditor(const entry_ref &ref, int32 lineNum)
1520 {
1521 	BMessage message(B_REFS_RECEIVED);
1522 	message.AddRef("refs", &ref);
1523 
1524 	if (lineNum != -1) {
1525 		message.AddInt32("line", lineNum);	// for Pe
1526 		message.AddInt32("be:line", lineNum);
1527 	}
1528 
1529 	// Find the preferred code editor
1530 	char editorSig[B_MIME_TYPE_LENGTH];
1531 	BMimeType mimeType("text/x-source-code");
1532 	mimeType.GetPreferredApp(editorSig);
1533 
1534 	entry_ref editor;
1535 	if (be_roster->FindApp(editorSig, &editor) != B_OK)
1536 		return false;
1537 
1538 	if (be_roster->IsRunning(&editor)) {
1539 		BMessenger msngr(NULL, be_roster->TeamFor(&editor));
1540 		if (msngr.SendMessage(&message) != B_OK)
1541 			return false;
1542 	} else {
1543 		if (be_roster->Launch(&editor, &message) != B_OK)
1544 			return false;
1545 	}
1546 
1547 	return true;
1548 }
1549 
1550 
1551 void
1552 GrepWindow::_RemoveFolderListDuplicates(BList* folderList)
1553 {
1554 	if (folderList == NULL)
1555 		return;
1556 
1557 	int32 folderCount = folderList->CountItems();
1558 	BString folderX;
1559 	BString folderY;
1560 
1561 	for (int32 x = 0; x < folderCount; x++) {
1562 		BPath* path = static_cast<BPath*>(folderList->ItemAt(x));
1563 		folderX = path->Path();
1564 
1565 		for (int32 y = x + 1; y < folderCount; y++) {
1566 			path = static_cast<BPath*>(folderList->ItemAt(y));
1567 			folderY = path->Path();
1568 			if (folderX == folderY) {
1569 				delete static_cast<BPath*>(folderList->RemoveItem(y));
1570 				folderCount--;
1571 				y--;
1572 			}
1573 		}
1574 	}
1575 }
1576 
1577 
1578 status_t
1579 GrepWindow::_OpenFoldersInTracker(BList* folderList)
1580 {
1581 	status_t status = B_OK;
1582 	BMessage refsMsg(B_REFS_RECEIVED);
1583 
1584 	int32 folderCount = folderList->CountItems();
1585 	for (int32 index = 0; index < folderCount; index++) {
1586 		BPath* path = static_cast<BPath*>(folderList->ItemAt(index));
1587 
1588 		entry_ref folderRef;
1589 		status = get_ref_for_path(path->Path(), &folderRef);
1590 		if (status != B_OK)
1591 			return status;
1592 
1593 		status = refsMsg.AddRef("refs", &folderRef);
1594 		if (status != B_OK)
1595 			return status;
1596 	}
1597 
1598 	status = be_roster->Launch(TRACKER_SIGNATURE, &refsMsg);
1599 	if (status != B_OK && status != B_ALREADY_RUNNING)
1600 		return status;
1601 
1602 	return B_OK;
1603 }
1604 
1605 
1606 bool
1607 GrepWindow::_AreAllFoldersOpenInTracker(BList* folderList)
1608 {
1609 	// Compare the folders we want open in Tracker to
1610 	// the actual Tracker windows currently open.
1611 
1612 	// We build a list of open Tracker windows, and compare
1613 	// it to the list of folders we want open in Tracker.
1614 
1615 	// If all folders exists in the list of Tracker windows
1616 	// return true
1617 
1618 	status_t status = B_OK;
1619 	BMessenger trackerMessenger(TRACKER_SIGNATURE);
1620 	BMessage sendMessage;
1621 	BMessage replyMessage;
1622 	BList windowList;
1623 
1624 	if (!trackerMessenger.IsValid())
1625 		return false;
1626 
1627 	for (int32 count = 1; ; count++) {
1628 		sendMessage.MakeEmpty();
1629 		replyMessage.MakeEmpty();
1630 
1631 		sendMessage.what = B_GET_PROPERTY;
1632 		sendMessage.AddSpecifier("Path");
1633 		sendMessage.AddSpecifier("Poses");
1634 		sendMessage.AddSpecifier("Window", count);
1635 
1636 		status = trackerMessenger.SendMessage(&sendMessage, &replyMessage);
1637 		if (status != B_OK)
1638 			return false;
1639 
1640 		entry_ref* trackerRef = new (nothrow) entry_ref;
1641 		status = replyMessage.FindRef("result", trackerRef);
1642 		if (status != B_OK || !windowList.AddItem(trackerRef)) {
1643 			delete trackerRef;
1644 			break;
1645 		}
1646 	}
1647 
1648 	int32 folderCount = folderList->CountItems();
1649 	int32 windowCount = windowList.CountItems();
1650 
1651 	int32 found = 0;
1652 	BPath* folderPath;
1653 	entry_ref* windowRef;
1654 	BString folderString;
1655 	BString windowString;
1656 	bool result = false;
1657 
1658 	if (folderCount > windowCount) {
1659 		// at least one folder is not open in Tracker
1660 		goto out;
1661 	}
1662 
1663 	// Loop over the two lists and see if all folders exist as window
1664 	for (int32 x = 0; x < folderCount; x++) {
1665 		for (int32 y = 0; y < windowCount; y++) {
1666 
1667 			folderPath = static_cast<BPath*>(folderList->ItemAt(x));
1668 			windowRef = static_cast<entry_ref*>(windowList.ItemAt(y));
1669 
1670 			if (folderPath == NULL)
1671 				break;
1672 
1673 			if (windowRef == NULL)
1674 				break;
1675 
1676 			folderString = folderPath->Path();
1677 
1678 			BEntry entry;
1679 			BPath path;
1680 
1681 			if (entry.SetTo(windowRef) == B_OK && path.SetTo(&entry) == B_OK) {
1682 
1683 				windowString = path.Path();
1684 
1685 				if (folderString == windowString) {
1686 					found++;
1687 					break;
1688 				}
1689 			}
1690 		}
1691 	}
1692 
1693 	result = found == folderCount;
1694 
1695 out:
1696 	// delete list of window entry_refs
1697 	for (int32 x = 0; x < windowCount; x++)
1698 		delete static_cast<entry_ref*>(windowList.ItemAt(x));
1699 
1700 	return result;
1701 }
1702 
1703 
1704 status_t
1705 GrepWindow::_SelectFilesInTracker(BList* folderList, BMessage* refsMessage)
1706 {
1707 	// loops over Tracker windows, find each windowRef,
1708 	// extract the refs that are children of windowRef,
1709 	// add refs to selection-message
1710 
1711 	status_t status = B_OK;
1712 	BMessenger trackerMessenger(TRACKER_SIGNATURE);
1713 	BMessage windowSendMessage;
1714 	BMessage windowReplyMessage;
1715 	BMessage selectionSendMessage;
1716 	BMessage selectionReplyMessage;
1717 
1718 	if (!trackerMessenger.IsValid())
1719 		return status;
1720 
1721 	// loop over Tracker windows
1722 	for (int32 windowCount = 1; ; windowCount++) {
1723 
1724 		windowSendMessage.MakeEmpty();
1725 		windowReplyMessage.MakeEmpty();
1726 
1727 		windowSendMessage.what = B_GET_PROPERTY;
1728 		windowSendMessage.AddSpecifier("Path");
1729 		windowSendMessage.AddSpecifier("Poses");
1730 		windowSendMessage.AddSpecifier("Window", windowCount);
1731 
1732 		status = trackerMessenger.SendMessage(&windowSendMessage,
1733 			&windowReplyMessage);
1734 
1735 		if (status != B_OK)
1736 			return status;
1737 
1738 		entry_ref windowRef;
1739 		status = windowReplyMessage.FindRef("result", &windowRef);
1740 		if (status != B_OK)
1741 			break;
1742 
1743 		int32 folderCount = folderList->CountItems();
1744 
1745 		// loop over folders in folderList
1746 		for (int32 x = 0; x < folderCount; x++) {
1747 			BPath* folderPath = static_cast<BPath*>(folderList->ItemAt(x));
1748 			if (folderPath == NULL)
1749 				break;
1750 
1751 			BString folderString = folderPath->Path();
1752 
1753 			BEntry windowEntry;
1754 			BPath windowPath;
1755 			BString windowString;
1756 
1757 			status = windowEntry.SetTo(&windowRef);
1758 			if (status != B_OK)
1759 				break;
1760 
1761 			status = windowPath.SetTo(&windowEntry);
1762 			if (status != B_OK)
1763 				break;
1764 
1765 			windowString = windowPath.Path();
1766 
1767 			// if match, loop over items in refsMessage
1768 			// and add those that live in window/folder
1769 			// to a selection message
1770 
1771 			if (windowString == folderString) {
1772 				selectionSendMessage.MakeEmpty();
1773 				selectionSendMessage.what = B_SET_PROPERTY;
1774 				selectionReplyMessage.MakeEmpty();
1775 
1776 				// loop over refs and add to message
1777 				entry_ref ref;
1778 				for (int32 index = 0; ; index++) {
1779 					status = refsMessage->FindRef("refs", index, &ref);
1780 					if (status != B_OK)
1781 						break;
1782 
1783 					BDirectory directory(&windowRef);
1784 					BEntry entry(&ref);
1785 					if (directory.Contains(&entry))
1786 						selectionSendMessage.AddRef("data", &ref);
1787 				}
1788 
1789 				// finish selection message
1790 				selectionSendMessage.AddSpecifier("Selection");
1791 				selectionSendMessage.AddSpecifier("Poses");
1792 				selectionSendMessage.AddSpecifier("Window", windowCount);
1793 
1794 				trackerMessenger.SendMessage(&selectionSendMessage,
1795 					&selectionReplyMessage);
1796 			}
1797 		}
1798 	}
1799 
1800 	return B_OK;
1801 }
1802