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