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