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