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