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