xref: /haiku/src/apps/text_search/GrepWindow.cpp (revision 7749d0bb0c358a3279b1b9cc76d8376e900130a5)
1 /*
2  * Copyright (c) 1998-2007 Matthijs Hollemans
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20  * DEALINGS IN THE SOFTWARE.
21  */
22 #include "GrepWindow.h"
23 
24 #include <ctype.h>
25 #include <errno.h>
26 #include <new>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 
32 #include <Application.h>
33 #include <AppFileInfo.h>
34 #include <Alert.h>
35 #include <Clipboard.h>
36 #include <MessageRunner.h>
37 #include <Path.h>
38 #include <PathMonitor.h>
39 #include <Roster.h>
40 #include <String.h>
41 #include <UTF8.h>
42 
43 #include "ChangesIterator.h"
44 #include "GlobalDefs.h"
45 #include "Grepper.h"
46 #include "InitialIterator.h"
47 #include "Translation.h"
48 
49 #undef B_TRANSLATE_CONTEXT
50 #define B_TRANSLATE_CONTEXT "GrepWindow"
51 
52 
53 using std::nothrow;
54 
55 static const bigtime_t kChangesPulseInterval = 150000;
56 
57 #define TRACE_NODE_MONITORING
58 #ifdef TRACE_NODE_MONITORING
59 # define TRACE_NM(x...) printf(x)
60 #else
61 # define TRACE_NM(x...)
62 #endif
63 
64 //#define TRACE_FUNCTIONS
65 #ifdef TRACE_FUNCTIONS
66 	class FunctionTracer {
67 	public:
68 		FunctionTracer(const char* functionName)
69 			: fName(functionName)
70 		{
71 			printf("%s - enter\n", fName.String());
72 		}
73 		~FunctionTracer()
74 		{
75 			printf("%s - exit\n", fName.String());
76 		}
77 	private:
78 		BString	fName;
79 	};
80 # define CALLED()	FunctionTracer functionTracer(__PRETTY_FUNCTION__)
81 #else
82 # define CALLED()
83 #endif // TRACE_FUNCTIONS
84 
85 
86 GrepWindow::GrepWindow(BMessage* message)
87 	: BWindow(BRect(0, 0, 1, 1), NULL, B_DOCUMENT_WINDOW, 0),
88 	  fSearchText(NULL),
89 	  fSearchResults(NULL),
90 	  fMenuBar(NULL),
91 	  fFileMenu(NULL),
92 	  fNew(NULL),
93 	  fOpen(NULL),
94 	  fClose(NULL),
95 	  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 	if (item == NULL) {
1053 		item = new ResultItem(ref);
1054 		fSearchResults->AddItem(item);
1055 		item->SetExpanded(fModel->fShowContents);
1056 	}
1057 
1058 	const char* buf;
1059 	while (message->FindString("text", --count, &buf) == B_OK) {
1060 		uchar* temp = (uchar*)strdup(buf);
1061 		uchar* ptr = temp;
1062 
1063 		while (true) {
1064 			// replace all non-printable characters by spaces
1065 			uchar c = *ptr;
1066 
1067 			if (c == '\0')
1068 				break;
1069 
1070 			if (!(c & 0x80) && iscntrl(c))
1071 				*ptr = ' ';
1072 
1073 			++ptr;
1074 		}
1075 
1076 		fSearchResults->AddUnder(new BStringItem((const char*)temp), item);
1077 
1078 		free(temp);
1079 	}
1080 }
1081 
1082 
1083 void
1084 GrepWindow::_OnReportError(BMessage *message)
1085 {
1086 	const char* buf;
1087 	if (message->FindString("error", &buf) == B_OK)
1088 		fSearchResults->AddItem(new BStringItem(buf));
1089 }
1090 
1091 
1092 void
1093 GrepWindow::_OnRecurseLinks()
1094 {
1095 	fModel->fRecurseLinks = !fModel->fRecurseLinks;
1096 	fRecurseLinks->SetMarked(fModel->fRecurseLinks);
1097 	_ModelChanged();
1098 }
1099 
1100 
1101 void
1102 GrepWindow::_OnRecurseDirs()
1103 {
1104 	fModel->fRecurseDirs = !fModel->fRecurseDirs;
1105 	fRecurseDirs->SetMarked(fModel->fRecurseDirs);
1106 	_ModelChanged();
1107 }
1108 
1109 
1110 void
1111 GrepWindow::_OnSkipDotDirs()
1112 {
1113 	fModel->fSkipDotDirs = !fModel->fSkipDotDirs;
1114 	fSkipDotDirs->SetMarked(fModel->fSkipDotDirs);
1115 	_ModelChanged();
1116 }
1117 
1118 
1119 void
1120 GrepWindow::_OnEscapeText()
1121 {
1122 	fModel->fEscapeText = !fModel->fEscapeText;
1123 	fEscapeText->SetMarked(fModel->fEscapeText);
1124 	_ModelChanged();
1125 }
1126 
1127 
1128 void
1129 GrepWindow::_OnCaseSensitive()
1130 {
1131 	fModel->fCaseSensitive = !fModel->fCaseSensitive;
1132 	fCaseSensitive->SetMarked(fModel->fCaseSensitive);
1133 	_ModelChanged();
1134 }
1135 
1136 
1137 void
1138 GrepWindow::_OnTextOnly()
1139 {
1140 	fModel->fTextOnly = !fModel->fTextOnly;
1141 	fTextOnly->SetMarked(fModel->fTextOnly);
1142 	_ModelChanged();
1143 }
1144 
1145 
1146 void
1147 GrepWindow::_OnInvokePe()
1148 {
1149 	fModel->fInvokePe = !fModel->fInvokePe;
1150 	fInvokePe->SetMarked(fModel->fInvokePe);
1151 	_SavePrefs();
1152 }
1153 
1154 
1155 void
1156 GrepWindow::_OnCheckboxShowLines()
1157 {
1158 	// toggle checkbox and menuitem
1159 	fModel->fShowContents = !fModel->fShowContents;
1160 	fShowLinesMenuitem->SetMarked(!fShowLinesMenuitem->IsMarked());
1161 
1162 	// Selection in BOutlineListView in multiple selection mode
1163 	// gets weird when collapsing. I've tried all sorts of things.
1164 	// It seems impossible to make it behave just right.
1165 
1166 	// Going from collapsed to expande mode, the superitems
1167 	// keep their selection, the subitems don't (yet) have
1168 	// a selection. This works as expected, AFAIK.
1169 
1170 	// Going from expanded to collapsed mode, I would like
1171 	// for a selected subitem (line) to select its superitem,
1172 	// (its file) and the subitem be unselected.
1173 
1174 	// I've successfully tried code patches that apply the
1175 	// selection pattern that I want, but with weird effects
1176 	// on subsequent manual selection.
1177 	// Lines stay selected while the user tries to select
1178 	// some other line. It just gets weird.
1179 
1180 	// It's as though listItem->Select() and Deselect()
1181 	// put the items in some semi-selected state.
1182 	// Or maybe I've got it all wrong.
1183 
1184 	// So, here's the plain basic collapse/expand.
1185 	// I think it's the least bad of what's possible on BeOS R5,
1186 	// but perhaps someone comes along with a patch of magic.
1187 
1188 	int32 numItems = fSearchResults->FullListCountItems();
1189 	for (int32 x = 0; x < numItems; ++x) {
1190 		BListItem* listItem = fSearchResults->FullListItemAt(x);
1191 		if (listItem->OutlineLevel() == 0) {
1192 			if (fModel->fShowContents) {
1193 				if (!fSearchResults->IsExpanded(x))
1194 					fSearchResults->Expand(listItem);
1195 			} else {
1196 				if (fSearchResults->IsExpanded(x))
1197 					fSearchResults->Collapse(listItem);
1198 			}
1199 		}
1200 	}
1201 
1202 	fSearchResults->Invalidate();
1203 
1204 	_SavePrefs();
1205 }
1206 
1207 
1208 void
1209 GrepWindow::_OnMenuShowLines()
1210 {
1211 	// toggle companion checkbox
1212 	fShowLinesCheckbox->SetValue(!fShowLinesCheckbox->Value());
1213 	_OnCheckboxShowLines();
1214 }
1215 
1216 
1217 void
1218 GrepWindow::_OnInvokeItem()
1219 {
1220 	for (int32 selectionIndex = 0; ; selectionIndex++) {
1221 		int32 itemIndex = fSearchResults->CurrentSelection(selectionIndex);
1222 		BListItem* item = fSearchResults->ItemAt(itemIndex);
1223 		if (item == NULL)
1224 			break;
1225 
1226 		int32 level = item->OutlineLevel();
1227 		int32 lineNum = -1;
1228 
1229 		// Get the line number.
1230 		// only this level has line numbers
1231 		if (level == 1) {
1232 			BStringItem *str = dynamic_cast<BStringItem*>(item);
1233 			if (str != NULL) {
1234 				lineNum = atol(str->Text());
1235 					// fortunately, atol knows when to stop the conversion
1236 			}
1237 		}
1238 
1239 		// Get the top-most item and launch its entry_ref.
1240 		while (level != 0) {
1241 			item = fSearchResults->Superitem(item);
1242 			if (item == NULL)
1243 				break;
1244 			level = item->OutlineLevel();
1245 		}
1246 
1247 		ResultItem* entry = dynamic_cast<ResultItem*>(item);
1248 		if (entry != NULL) {
1249 			bool done = false;
1250 
1251 			if (fModel->fInvokePe)
1252 				done = _OpenInPe(entry->ref, lineNum);
1253 
1254 			if (!done)
1255 				be_roster->Launch(&entry->ref);
1256 		}
1257 	}
1258 }
1259 
1260 
1261 void
1262 GrepWindow::_OnSearchText()
1263 {
1264 	CALLED();
1265 
1266 	bool enabled = fSearchText->TextView()->TextLength() != 0;
1267 	fButton->SetEnabled(enabled);
1268 	fSearch->SetEnabled(enabled);
1269 	_StopNodeMonitoring();
1270 }
1271 
1272 
1273 void
1274 GrepWindow::_OnHistoryItem(BMessage* message)
1275 {
1276 	const char* buf;
1277 	if (message->FindString("text", &buf) == B_OK)
1278 		fSearchText->SetText(buf);
1279 }
1280 
1281 
1282 void
1283 GrepWindow::_OnTrimSelection()
1284 {
1285 	if (fSearchResults->CurrentSelection() < 0) {
1286 		BString text;
1287 		text << B_TRANSLATE("Please select the files you wish to keep searching.");
1288 		text << "\n";
1289 		text << B_TRANSLATE("The unselected files will be removed from the list.");
1290 		text << "\n";
1291 		BAlert* alert = new BAlert(NULL, text.String(), B_TRANSLATE("OK"), NULL, NULL,
1292 			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1293 		alert->Go(NULL);
1294 		return;
1295 	}
1296 
1297 	BMessage message;
1298 	BString path;
1299 
1300 	for (int32 index = 0; ; index++) {
1301 		BStringItem* item = dynamic_cast<BStringItem*>(
1302 			fSearchResults->ItemAt(index));
1303 		if (item == NULL)
1304 			break;
1305 
1306 		if (!item->IsSelected() || item->OutlineLevel() != 0)
1307 			continue;
1308 
1309 		if (path == item->Text())
1310 			continue;
1311 
1312 		path = item->Text();
1313 		entry_ref ref;
1314 		if (get_ref_for_path(path.String(), &ref) == B_OK)
1315 			message.AddRef("refs", &ref);
1316 	}
1317 
1318 	fModel->fDirectory = entry_ref();
1319 		// invalidated on purpose
1320 
1321 	fModel->fSelectedFiles.MakeEmpty();
1322 	fModel->fSelectedFiles = message;
1323 
1324 	PostMessage(MSG_START_CANCEL);
1325 
1326 	_SetWindowTitle();
1327 }
1328 
1329 
1330 void
1331 GrepWindow::_OnCopyText()
1332 {
1333 	bool onlyCopySelection = true;
1334 
1335 	if (fSearchResults->CurrentSelection() < 0)
1336 		onlyCopySelection = false;
1337 
1338 	BString buffer;
1339 
1340 	for (int32 index = 0; ; index++) {
1341 		BStringItem* item = dynamic_cast<BStringItem*>(
1342 			fSearchResults->ItemAt(index));
1343 		if (item == NULL)
1344 			break;
1345 
1346 		if (onlyCopySelection) {
1347 			if (item->IsSelected())
1348 				buffer << item->Text() << "\n";
1349 		} else
1350 			buffer << item->Text() << "\n";
1351 	}
1352 
1353 	status_t status = B_OK;
1354 
1355 	BMessage* clip = NULL;
1356 
1357 	if (be_clipboard->Lock()) {
1358 		be_clipboard->Clear();
1359 
1360 		clip = be_clipboard->Data();
1361 
1362 		clip->AddData("text/plain", B_MIME_TYPE, buffer.String(),
1363 			buffer.Length());
1364 
1365 		status = be_clipboard->Commit();
1366 
1367 		if (status != B_OK) {
1368 			be_clipboard->Unlock();
1369 			return;
1370 		}
1371 
1372 		be_clipboard->Unlock();
1373 	}
1374 }
1375 
1376 
1377 void
1378 GrepWindow::_OnSelectInTracker()
1379 {
1380 	if (fSearchResults->CurrentSelection() < 0) {
1381 		BAlert* alert = new BAlert("Info",
1382 			B_TRANSLATE("Please select the files you wish to have selected for you in "
1383 				"Tracker."),
1384 			B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1385 		alert->Go(NULL);
1386 		return;
1387 	}
1388 
1389 	BMessage message;
1390 	BString filePath;
1391 	BPath folderPath;
1392 	BList folderList;
1393 	BString lastFolderAddedToList;
1394 
1395 	for (int32 index = 0; ; index++) {
1396 		BStringItem* item = dynamic_cast<BStringItem*>(
1397 			fSearchResults->ItemAt(index));
1398 		if (item == NULL)
1399 			break;
1400 
1401 		// only open selected and top level (file) items
1402 		if (!item->IsSelected() || item->OutlineLevel() > 0)
1403 			continue;
1404 
1405 		// check if this was previously opened
1406 		if (filePath == item->Text())
1407 			continue;
1408 
1409 		filePath = item->Text();
1410 		entry_ref file_ref;
1411 		if (get_ref_for_path(filePath.String(), &file_ref) != B_OK)
1412 			continue;
1413 
1414 		message.AddRef("refs", &file_ref);
1415 
1416 		// add parent folder to list of folders to open
1417 		folderPath.SetTo(filePath.String());
1418 		if (folderPath.GetParent(&folderPath) == B_OK) {
1419 			BPath* path = new BPath(folderPath);
1420 			if (path->Path() != lastFolderAddedToList) {
1421 				// catches some duplicates
1422 				folderList.AddItem(path);
1423 				lastFolderAddedToList = path->Path();
1424 			} else
1425 				delete path;
1426 		}
1427 	}
1428 
1429 	_RemoveFolderListDuplicates(&folderList);
1430 	_OpenFoldersInTracker(&folderList);
1431 
1432 	int32 aShortWhile = 100000;
1433 	snooze(aShortWhile);
1434 
1435 	if (!_AreAllFoldersOpenInTracker(&folderList)) {
1436 		for (int32 x = 0; x < 5; x++) {
1437 			aShortWhile += 100000;
1438 			snooze(aShortWhile);
1439 			_OpenFoldersInTracker(&folderList);
1440 		}
1441 	}
1442 
1443 	if (!_AreAllFoldersOpenInTracker(&folderList)) {
1444 		BString str1;
1445 		str1 << B_TRANSLATE("%APP_NAME couldn't open one or more folders.");
1446 		str1.ReplaceFirst("%APP_NAME",APP_NAME);
1447 		BAlert* alert = new BAlert(NULL, str1.String(), B_TRANSLATE("OK"),
1448 			NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
1449 		alert->Go(NULL);
1450 		goto out;
1451 	}
1452 
1453 	_SelectFilesInTracker(&folderList, &message);
1454 
1455 out:
1456 	// delete folderList contents
1457 	int32 folderCount = folderList.CountItems();
1458 	for (int32 x = 0; x < folderCount; x++)
1459 		delete static_cast<BPath*>(folderList.ItemAt(x));
1460 }
1461 
1462 
1463 void
1464 GrepWindow::_OnQuitNow()
1465 {
1466 	if (be_app->Lock()) {
1467 		be_app->PostMessage(B_QUIT_REQUESTED);
1468 		be_app->Unlock();
1469 	}
1470 }
1471 
1472 
1473 void
1474 GrepWindow::_OnFileDrop(BMessage* message)
1475 {
1476 	if (fModel->fState != STATE_IDLE)
1477 		return;
1478 
1479 	entry_ref directory;
1480 	_InitRefsReceived(&directory, message);
1481 
1482 	fModel->fDirectory = directory;
1483 	fModel->fSelectedFiles.MakeEmpty();
1484 	fModel->fSelectedFiles = *message;
1485 
1486 	fSearchResults->MakeEmpty();
1487 
1488 	_SetWindowTitle();
1489 }
1490 
1491 
1492 void
1493 GrepWindow::_OnRefsReceived(BMessage* message)
1494 {
1495 	_OnFileDrop(message);
1496 	// It seems a B_CANCEL always follows a B_REFS_RECEIVED
1497 	// from a BFilePanel in Open mode.
1498 	//
1499 	// _OnOpenPanelCancel() is called on B_CANCEL.
1500 	// That's where saving the current dir of the file panel occurs, for now,
1501 	// and also the neccesary deletion of the file panel object.
1502 	// A hidden file panel would otherwise jam the shutdown process.
1503 }
1504 
1505 
1506 void
1507 GrepWindow::_OnOpenPanel()
1508 {
1509 	if (fFilePanel != NULL)
1510 		return;
1511 
1512 	entry_ref path;
1513 	if (get_ref_for_path(fModel->fFilePanelPath.String(), &path) != B_OK)
1514 		return;
1515 
1516 	BMessenger messenger(this);
1517 	BMessage message(MSG_REFS_RECEIVED);
1518 	fFilePanel = new BFilePanel(B_OPEN_PANEL, &messenger, &path,
1519 		B_FILE_NODE | B_DIRECTORY_NODE | B_SYMLINK_NODE, true,
1520 		&message, NULL, true, true);
1521 
1522 	fFilePanel->Show();
1523 }
1524 
1525 
1526 void
1527 GrepWindow::_OnOpenPanelCancel()
1528 {
1529 	entry_ref panelDirRef;
1530 	fFilePanel->GetPanelDirectory(&panelDirRef);
1531 	BPath path(&panelDirRef);
1532 	fModel->fFilePanelPath = path.Path();
1533 	delete fFilePanel;
1534 	fFilePanel = NULL;
1535 }
1536 
1537 
1538 void
1539 GrepWindow::_OnSelectAll(BMessage *message)
1540 {
1541 	BMessenger messenger(fSearchResults);
1542 	messenger.SendMessage(B_SELECT_ALL);
1543 }
1544 
1545 
1546 void
1547 GrepWindow::_OnNewWindow()
1548 {
1549 	BMessage cloneRefs;
1550 		// we don't want GrepWindow::InitRefsReceived()
1551 		// to mess with the refs of the current window
1552 
1553 	cloneRefs = fModel->fSelectedFiles;
1554 	cloneRefs.AddRef("dir_ref", &(fModel->fDirectory));
1555 
1556 	new GrepWindow(&cloneRefs);
1557 }
1558 
1559 
1560 // #pragma mark -
1561 
1562 
1563 void
1564 GrepWindow::_ModelChanged()
1565 {
1566 	CALLED();
1567 
1568 	_StopNodeMonitoring();
1569 	_SavePrefs();
1570 }
1571 
1572 bool
1573 GrepWindow::_OpenInPe(const entry_ref &ref, int32 lineNum)
1574 {
1575 	BMessage message('Cmdl');
1576 	message.AddRef("refs", &ref);
1577 
1578 	if (lineNum != -1)
1579 		message.AddInt32("line", lineNum);
1580 
1581 	entry_ref pe;
1582 	if (be_roster->FindApp(PE_SIGNATURE, &pe) != B_OK)
1583 		return false;
1584 
1585 	if (be_roster->IsRunning(&pe)) {
1586 		BMessenger msngr(NULL, be_roster->TeamFor(&pe));
1587 		if (msngr.SendMessage(&message) != B_OK)
1588 			return false;
1589 	} else {
1590 		if (be_roster->Launch(&pe, &message) != B_OK)
1591 			return false;
1592 	}
1593 
1594 	return true;
1595 }
1596 
1597 
1598 void
1599 GrepWindow::_RemoveFolderListDuplicates(BList* folderList)
1600 {
1601 	if (folderList == NULL)
1602 		return;
1603 
1604 	int32 folderCount = folderList->CountItems();
1605 	BString folderX;
1606 	BString folderY;
1607 
1608 	for (int32 x = 0; x < folderCount; x++) {
1609 		BPath* path = static_cast<BPath*>(folderList->ItemAt(x));
1610 		folderX = path->Path();
1611 
1612 		for (int32 y = x + 1; y < folderCount; y++) {
1613 			path = static_cast<BPath*>(folderList->ItemAt(y));
1614 			folderY = path->Path();
1615 			if (folderX == folderY) {
1616 				delete static_cast<BPath*>(folderList->RemoveItem(y));
1617 				folderCount--;
1618 				y--;
1619 			}
1620 		}
1621 	}
1622 }
1623 
1624 
1625 status_t
1626 GrepWindow::_OpenFoldersInTracker(BList* folderList)
1627 {
1628 	status_t status = B_OK;
1629 	BMessage refsMsg(B_REFS_RECEIVED);
1630 
1631 	int32 folderCount = folderList->CountItems();
1632 	for (int32 index = 0; index < folderCount; index++) {
1633 		BPath* path = static_cast<BPath*>(folderList->ItemAt(index));
1634 
1635 		entry_ref folderRef;
1636 		status = get_ref_for_path(path->Path(), &folderRef);
1637 		if (status != B_OK)
1638 			return status;
1639 
1640 		status = refsMsg.AddRef("refs", &folderRef);
1641 		if (status != B_OK)
1642 			return status;
1643 	}
1644 
1645 	status = be_roster->Launch(TRACKER_SIGNATURE, &refsMsg);
1646 	if (status != B_OK && status != B_ALREADY_RUNNING)
1647 		return status;
1648 
1649 	return B_OK;
1650 }
1651 
1652 
1653 bool
1654 GrepWindow::_AreAllFoldersOpenInTracker(BList *folderList)
1655 {
1656 	// Compare the folders we want open in Tracker to
1657 	// the actual Tracker windows currently open.
1658 
1659 	// We build a list of open Tracker windows, and compare
1660 	// it to the list of folders we want open in Tracker.
1661 
1662 	// If all folders exists in the list of Tracker windows
1663 	// return true
1664 
1665 	status_t status = B_OK;
1666 	BMessenger trackerMessenger(TRACKER_SIGNATURE);
1667 	BMessage sendMessage;
1668 	BMessage replyMessage;
1669 	BList windowList;
1670 
1671 	if (!trackerMessenger.IsValid())
1672 		return false;
1673 
1674 	for (int32 count = 1; ; count++) {
1675 		sendMessage.MakeEmpty();
1676 		replyMessage.MakeEmpty();
1677 
1678 		sendMessage.what = B_GET_PROPERTY;
1679 		sendMessage.AddSpecifier("Path");
1680 		sendMessage.AddSpecifier("Poses");
1681 		sendMessage.AddSpecifier("Window", count);
1682 
1683 		status = trackerMessenger.SendMessage(&sendMessage, &replyMessage);
1684 		if (status != B_OK)
1685 			return false;
1686 
1687 		entry_ref* trackerRef = new (nothrow) entry_ref;
1688 		status = replyMessage.FindRef("result", trackerRef);
1689 		if (status != B_OK || !windowList.AddItem(trackerRef)) {
1690 			delete trackerRef;
1691 			break;
1692 		}
1693 	}
1694 
1695 	int32 folderCount = folderList->CountItems();
1696 	int32 windowCount = windowList.CountItems();
1697 
1698 	int32 found = 0;
1699 	BPath* folderPath;
1700 	entry_ref* windowRef;
1701 	BString folderString;
1702 	BString windowString;
1703 	bool result = false;
1704 
1705 	if (folderCount > windowCount) {
1706 		// at least one folder is not open in Tracker
1707 		goto out;
1708 	}
1709 
1710 	// Loop over the two lists and see if all folders exist as window
1711 	for (int32 x = 0; x < folderCount; x++) {
1712 		for (int32 y = 0; y < windowCount; y++) {
1713 
1714 			folderPath = static_cast<BPath*>(folderList->ItemAt(x));
1715 			windowRef = static_cast<entry_ref*>(windowList.ItemAt(y));
1716 
1717 			if (folderPath == NULL)
1718 				break;
1719 
1720 			if (windowRef == NULL)
1721 				break;
1722 
1723 			folderString = folderPath->Path();
1724 
1725 			BEntry entry;
1726 			BPath path;
1727 
1728 			if (entry.SetTo(windowRef) == B_OK && path.SetTo(&entry) == B_OK) {
1729 
1730 				windowString = path.Path();
1731 
1732 				if (folderString == windowString) {
1733 					found++;
1734 					break;
1735 				}
1736 			}
1737 		}
1738 	}
1739 
1740 	result = found == folderCount;
1741 
1742 out:
1743 	// delete list of window entry_refs
1744 	for (int32 x = 0; x < windowCount; x++)
1745 		delete static_cast<entry_ref*>(windowList.ItemAt(x));
1746 
1747 	return result;
1748 }
1749 
1750 
1751 status_t
1752 GrepWindow::_SelectFilesInTracker(BList* folderList, BMessage* refsMessage)
1753 {
1754 	// loops over Tracker windows, find each windowRef,
1755 	// extract the refs that are children of windowRef,
1756 	// add refs to selection-message
1757 
1758 	status_t status = B_OK;
1759 	BMessenger trackerMessenger(TRACKER_SIGNATURE);
1760 	BMessage windowSendMessage;
1761 	BMessage windowReplyMessage;
1762 	BMessage selectionSendMessage;
1763 	BMessage selectionReplyMessage;
1764 
1765 	if (!trackerMessenger.IsValid())
1766 		return status;
1767 
1768 	// loop over Tracker windows
1769 	for (int32 windowCount = 1; ; windowCount++) {
1770 
1771 		windowSendMessage.MakeEmpty();
1772 		windowReplyMessage.MakeEmpty();
1773 
1774 		windowSendMessage.what = B_GET_PROPERTY;
1775 		windowSendMessage.AddSpecifier("Path");
1776 		windowSendMessage.AddSpecifier("Poses");
1777 		windowSendMessage.AddSpecifier("Window", windowCount);
1778 
1779 		status = trackerMessenger.SendMessage(&windowSendMessage,
1780 			&windowReplyMessage);
1781 
1782 		if (status != B_OK)
1783 			return status;
1784 
1785 		entry_ref windowRef;
1786 		status = windowReplyMessage.FindRef("result", &windowRef);
1787 		if (status != B_OK)
1788 			break;
1789 
1790 		int32 folderCount = folderList->CountItems();
1791 
1792 		// loop over folders in folderList
1793 		for (int32 x = 0; x < folderCount; x++) {
1794 			BPath* folderPath = static_cast<BPath*>(folderList->ItemAt(x));
1795 			if (folderPath == NULL)
1796 				break;
1797 
1798 			BString folderString = folderPath->Path();
1799 
1800 			BEntry windowEntry;
1801 			BPath windowPath;
1802 			BString windowString;
1803 
1804 			status = windowEntry.SetTo(&windowRef);
1805 			if (status != B_OK)
1806 				break;
1807 
1808 			status = windowPath.SetTo(&windowEntry);
1809 			if (status != B_OK)
1810 				break;
1811 
1812 			windowString = windowPath.Path();
1813 
1814 			// if match, loop over items in refsMessage
1815 			// and add those that live in window/folder
1816 			// to a selection message
1817 
1818 			if (windowString == folderString) {
1819 				selectionSendMessage.MakeEmpty();
1820 				selectionSendMessage.what = B_SET_PROPERTY;
1821 				selectionReplyMessage.MakeEmpty();
1822 
1823 				// loop over refs and add to message
1824 				entry_ref ref;
1825 				for (int32 index = 0; ; index++) {
1826 					status = refsMessage->FindRef("refs", index, &ref);
1827 					if (status != B_OK)
1828 						break;
1829 
1830 					BDirectory directory(&windowRef);
1831 					BEntry entry(&ref);
1832 					if (directory.Contains(&entry))
1833 						selectionSendMessage.AddRef("data", &ref);
1834 				}
1835 
1836 				// finish selection message
1837 				selectionSendMessage.AddSpecifier("Selection");
1838 				selectionSendMessage.AddSpecifier("Poses");
1839 				selectionSendMessage.AddSpecifier("Window", windowCount);
1840 
1841 				trackerMessenger.SendMessage(&selectionSendMessage,
1842 					&selectionReplyMessage);
1843 			}
1844 		}
1845 	}
1846 
1847 	return B_OK;
1848 }
1849