xref: /haiku/src/apps/text_search/GrepWindow.cpp (revision 508f54795f39c3e7552d87c95aae9dd8ec6f505b)
1 /*
2  * Copyright (c) 1998-2007 Matthijs Hollemans
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20  * DEALINGS IN THE SOFTWARE.
21  */
22 #include "GrepWindow.h"
23 
24 #include <ctype.h>
25 #include <errno.h>
26 #include <new>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 
32 #include <Application.h>
33 #include <AppFileInfo.h>
34 #include <Alert.h>
35 #include <Clipboard.h>
36 #include <MessageRunner.h>
37 #include <Path.h>
38 #include <PathMonitor.h>
39 #include <Roster.h>
40 #include <String.h>
41 #include <UTF8.h>
42 
43 #include "ChangesIterator.h"
44 #include "GlobalDefs.h"
45 #include "Grepper.h"
46 #include "InitialIterator.h"
47 #include "Translation.h"
48 
49 #undef B_TRANSLATE_CONTEXT
50 #define B_TRANSLATE_CONTEXT "GrepWindow"
51 
52 
53 using std::nothrow;
54 
55 static const bigtime_t kChangesPulseInterval = 150000;
56 
57 #define TRACE_NODE_MONITORING
58 #ifdef TRACE_NODE_MONITORING
59 # define TRACE_NM(x...) printf(x)
60 #else
61 # define TRACE_NM(x...)
62 #endif
63 
64 //#define TRACE_FUNCTIONS
65 #ifdef TRACE_FUNCTIONS
66 	class FunctionTracer {
67 	public:
68 		FunctionTracer(const char* functionName)
69 			: fName(functionName)
70 		{
71 			printf("%s - enter\n", fName.String());
72 		}
73 		~FunctionTracer()
74 		{
75 			printf("%s - exit\n", fName.String());
76 		}
77 	private:
78 		BString	fName;
79 	};
80 # define CALLED()	FunctionTracer functionTracer(__PRETTY_FUNCTION__)
81 #else
82 # define CALLED()
83 #endif // TRACE_FUNCTIONS
84 
85 
86 GrepWindow::GrepWindow(BMessage* message)
87 	: BWindow(BRect(0, 0, 1, 1), NULL, B_DOCUMENT_WINDOW, 0),
88 	  fSearchText(NULL),
89 	  fSearchResults(NULL),
90 	  fMenuBar(NULL),
91 	  fFileMenu(NULL),
92 	  fNew(NULL),
93 	  fOpen(NULL),
94 	  fClose(NULL),
95 	  fAbout(NULL),
96 	  fQuit(NULL),
97 	  fActionMenu(NULL),
98 	  fSelectAll(NULL),
99 	  fSearch(NULL),
100 	  fTrimSelection(NULL),
101 	  fCopyText(NULL),
102 	  fSelectInTracker(NULL),
103 	  fOpenSelection(NULL),
104 	  fPreferencesMenu(NULL),
105 	  fRecurseLinks(NULL),
106 	  fRecurseDirs(NULL),
107 	  fSkipDotDirs(NULL),
108 	  fCaseSensitive(NULL),
109 	  fEscapeText(NULL),
110 	  fTextOnly(NULL),
111 	  fInvokePe(NULL),
112 	  fShowLinesMenuitem(NULL),
113 	  fHistoryMenu(NULL),
114 	  fEncodingMenu(NULL),
115 	  fUTF8(NULL),
116 	  fShiftJIS(NULL),
117 	  fEUC(NULL),
118 	  fJIS(NULL),
119 
120 	  fShowLinesCheckbox(NULL),
121 	  fButton(NULL),
122 
123 	  fGrepper(NULL),
124 	  fOldPattern(""),
125 	  fModel(new (nothrow) Model()),
126 	  fLastNodeMonitorEvent(system_time()),
127 	  fChangesIterator(NULL),
128 	  fChangesPulse(NULL),
129 
130 	  fFilePanel(NULL)
131 {
132 	if (fModel == NULL)
133 		return;
134 
135 	entry_ref directory;
136 	_InitRefsReceived(&directory, message);
137 
138 	fModel->fDirectory = directory;
139 	fModel->fSelectedFiles = *message;
140 
141 	_SetWindowTitle();
142 	_CreateMenus();
143 	_CreateViews();
144 	_LayoutViews();
145 	_LoadPrefs();
146 	_TileIfMultipleWindows();
147 
148 	Show();
149 }
150 
151 
152 GrepWindow::~GrepWindow()
153 {
154 	delete fGrepper;
155 	delete fModel;
156 }
157 
158 
159 void GrepWindow::FrameResized(float width, float height)
160 {
161 	BWindow::FrameResized(width, height);
162 	fModel->fFrame = Frame();
163 	_SavePrefs();
164 }
165 
166 
167 void GrepWindow::FrameMoved(BPoint origin)
168 {
169 	BWindow::FrameMoved(origin);
170 	fModel->fFrame = Frame();
171 	_SavePrefs();
172 }
173 
174 
175 void GrepWindow::MenusBeginning()
176 {
177 	fModel->FillHistoryMenu(fHistoryMenu);
178 	BWindow::MenusBeginning();
179 }
180 
181 
182 void GrepWindow::MenusEnded()
183 {
184 	for (int32 t = fHistoryMenu->CountItems(); t > 0; --t)
185 		delete fHistoryMenu->RemoveItem(t - 1);
186 
187 	BWindow::MenusEnded();
188 }
189 
190 
191 void GrepWindow::MessageReceived(BMessage *message)
192 {
193 	switch (message->what) {
194 		case B_ABOUT_REQUESTED:
195 			_OnAboutRequested();
196 			break;
197 
198 		case MSG_NEW_WINDOW:
199 			_OnNewWindow();
200 			break;
201 
202 		case B_SIMPLE_DATA:
203 			_OnFileDrop(message);
204 			break;
205 
206 		case MSG_OPEN_PANEL:
207 			_OnOpenPanel();
208 			break;
209 
210 		case MSG_REFS_RECEIVED:
211 			_OnRefsReceived(message);
212 			break;
213 
214 		case B_CANCEL:
215 			_OnOpenPanelCancel();
216 			break;
217 
218 		case MSG_RECURSE_LINKS:
219 			_OnRecurseLinks();
220 			break;
221 
222 		case MSG_RECURSE_DIRS:
223 			_OnRecurseDirs();
224 			break;
225 
226 		case MSG_SKIP_DOT_DIRS:
227 			_OnSkipDotDirs();
228 			break;
229 
230 		case MSG_CASE_SENSITIVE:
231 			_OnCaseSensitive();
232 			break;
233 
234 		case MSG_ESCAPE_TEXT:
235 			_OnEscapeText();
236 			break;
237 
238 		case MSG_TEXT_ONLY:
239 			_OnTextOnly();
240 			break;
241 
242 		case MSG_INVOKE_PE:
243 			_OnInvokePe();
244 			break;
245 
246 		case MSG_SEARCH_TEXT:
247 			_OnSearchText();
248 			break;
249 
250 		case MSG_SELECT_HISTORY:
251 			_OnHistoryItem(message);
252 			break;
253 
254 		case MSG_START_CANCEL:
255 			_OnStartCancel();
256 			break;
257 
258 		case MSG_SEARCH_FINISHED:
259 			_OnSearchFinished();
260 			break;
261 
262 		case MSG_START_NODE_MONITORING:
263 			_StartNodeMonitoring();
264 			break;
265 
266 		case B_PATH_MONITOR:
267 			_OnNodeMonitorEvent(message);
268 			break;
269 
270 		case MSG_NODE_MONITOR_PULSE:
271 			_OnNodeMonitorPulse();
272 			break;
273 
274 		case MSG_REPORT_FILE_NAME:
275 			_OnReportFileName(message);
276 			break;
277 
278 		case MSG_REPORT_RESULT:
279 			_OnReportResult(message);
280 			break;
281 
282 		case MSG_REPORT_ERROR:
283 			_OnReportError(message);
284 			break;
285 
286 		case MSG_SELECT_ALL:
287 			_OnSelectAll(message);
288 			break;
289 
290 		case MSG_TRIM_SELECTION:
291 			_OnTrimSelection();
292 			break;
293 
294 		case MSG_COPY_TEXT:
295 			_OnCopyText();
296 			break;
297 
298 		case MSG_SELECT_IN_TRACKER:
299 			_OnSelectInTracker();
300 			break;
301 
302 		case MSG_MENU_SHOW_LINES:
303 			_OnMenuShowLines();
304 			break;
305 
306 		case MSG_CHECKBOX_SHOW_LINES:
307 			_OnCheckboxShowLines();
308 			break;
309 
310 		case MSG_OPEN_SELECTION:
311 			// fall through
312 		case MSG_INVOKE_ITEM:
313 			_OnInvokeItem();
314 			break;
315 
316 		case MSG_QUIT_NOW:
317 			_OnQuitNow();
318 			break;
319 
320 		case 'utf8':
321 			fModel->fEncoding = 0;
322 			break;
323 
324 		case B_SJIS_CONVERSION:
325 			fModel->fEncoding = B_SJIS_CONVERSION;
326 			break;
327 
328 		case B_EUC_CONVERSION:
329 			fModel->fEncoding = B_EUC_CONVERSION;
330 			break;
331 
332 		case B_JIS_CONVERSION:
333 			fModel->fEncoding = B_JIS_CONVERSION;
334 			break;
335 
336 		default:
337 			BWindow::MessageReceived(message);
338 			break;
339 	}
340 }
341 
342 
343 void
344 GrepWindow::Quit()
345 {
346 	CALLED();
347 
348 	_StopNodeMonitoring();
349 	_SavePrefs();
350 
351 	// TODO: stippi: Looks like this could be done
352 	// by maintaining a counter in GrepApp with the number of open
353 	// grep windows... and just quit when it goes zero
354 	if (be_app->Lock()) {
355 		be_app->PostMessage(MSG_TRY_QUIT);
356 		be_app->Unlock();
357 		BWindow::Quit();
358 	}
359 }
360 
361 
362 // #pragma mark -
363 
364 
365 void
366 GrepWindow::_InitRefsReceived(entry_ref* directory, BMessage* message)
367 {
368 	// HACK-HACK-HACK:
369 	// If the user selected a single folder and invoked TextSearch on it,
370 	// but recurse directories is switched off, TextSearch would do nothing.
371 	// In that special case, we'd like it to recurse into that folder (but
372 	// not go any deeper after that).
373 
374 	type_code code;
375 	int32 count;
376 	message->GetInfo("refs", &code, &count);
377 
378 	if (count == 0) {
379 		if (message->FindRef("dir_ref", 0, directory) == B_OK)
380 			message->MakeEmpty();
381 	}
382 
383 	if (count == 1) {
384 		entry_ref ref;
385 		if (message->FindRef("refs", 0, &ref) == B_OK) {
386 			BEntry entry(&ref, true);
387 			if (entry.IsDirectory()) {
388 				// ok, special case, we use this folder as base directory
389 				// and pretend nothing had been selected:
390 				*directory = ref;
391 				message->MakeEmpty();
392 			}
393 		}
394 	}
395 }
396 
397 
398 void
399 GrepWindow::_SetWindowTitle()
400 {
401 	BEntry entry(&fModel->fDirectory, true);
402 	BString title;
403 	if (entry.InitCheck() == B_OK) {
404 		BPath path;
405 		if (entry.GetPath(&path) == B_OK)
406 			title << B_TRANSLATE(APP_NAME) << ": " << path.Path();
407 	}
408 
409 	if (!title.Length())
410 		title = B_TRANSLATE(APP_NAME);
411 
412 	SetTitle(title.String());
413 }
414 
415 
416 void
417 GrepWindow::_CreateMenus()
418 {
419 	fMenuBar = new BMenuBar(BRect(0,0,1,1), "menubar");
420 
421 	fFileMenu = new BMenu(B_TRANSLATE("File"));
422 	fActionMenu = new BMenu(B_TRANSLATE("Actions"));
423 	fPreferencesMenu = new BMenu(B_TRANSLATE("Settings"));
424 	fHistoryMenu = new BMenu(B_TRANSLATE("History"));
425 	fEncodingMenu = new BMenu(B_TRANSLATE("Encoding"));
426 
427 	fNew = new BMenuItem(
428 		B_TRANSLATE("New window"), new BMessage(MSG_NEW_WINDOW), 'N');
429 
430 	fOpen = new BMenuItem(
431 		B_TRANSLATE("Set which files to search"), new BMessage(MSG_OPEN_PANEL), 'F');
432 
433 	fClose = new BMenuItem(
434 		B_TRANSLATE("Close"), new BMessage(B_QUIT_REQUESTED), 'W');
435 
436 	fAbout = new BMenuItem(
437 		B_TRANSLATE("About TextSearch" B_UTF8_ELLIPSIS), new BMessage(B_ABOUT_REQUESTED));
438 
439 	fQuit = new BMenuItem(
440 		B_TRANSLATE("Quit"), new BMessage(MSG_QUIT_NOW), 'Q');
441 
442 	fSearch = new BMenuItem(
443 		B_TRANSLATE("Search"), new BMessage(MSG_START_CANCEL), 'S');
444 
445 	fSelectAll = new BMenuItem(
446 		B_TRANSLATE("Select all"), new BMessage(MSG_SELECT_ALL), 'A');
447 
448 	fTrimSelection = new BMenuItem(
449 		B_TRANSLATE("Trim to selection"), new BMessage(MSG_TRIM_SELECTION), 'T');
450 
451 	fOpenSelection = new BMenuItem(
452 		B_TRANSLATE("Open selection"), new BMessage(MSG_OPEN_SELECTION), 'O');
453 
454 	fSelectInTracker = new BMenuItem(
455 		B_TRANSLATE("Show files in Tracker"), new BMessage(MSG_SELECT_IN_TRACKER), 'K');
456 
457 	fCopyText = new BMenuItem(
458 		B_TRANSLATE("Copy text to clipboard"), new BMessage(MSG_COPY_TEXT), 'B');
459 
460 	fRecurseLinks = new BMenuItem(
461 		B_TRANSLATE("Follow symbolic links"), new BMessage(MSG_RECURSE_LINKS));
462 
463 	fRecurseDirs = new BMenuItem(
464 		B_TRANSLATE("Look in sub-folders"), new BMessage(MSG_RECURSE_DIRS));
465 
466 	fSkipDotDirs = new BMenuItem(
467 		B_TRANSLATE("Skip sub-folders starting with a dot"), new BMessage(MSG_SKIP_DOT_DIRS));
468 
469 	fCaseSensitive = new BMenuItem(
470 		B_TRANSLATE("Case-sensitive"), new BMessage(MSG_CASE_SENSITIVE));
471 
472 	fEscapeText = new BMenuItem(
473 		B_TRANSLATE("Escape search text"), new BMessage(MSG_ESCAPE_TEXT));
474 
475 	fTextOnly = new BMenuItem(
476 		B_TRANSLATE("Text files only"), new BMessage(MSG_TEXT_ONLY));
477 
478 	fInvokePe = new BMenuItem(
479 		B_TRANSLATE("Open files in Pe"), new BMessage(MSG_INVOKE_PE));
480 
481 	fShowLinesMenuitem = new BMenuItem(
482 		B_TRANSLATE("Show lines"), new BMessage(MSG_MENU_SHOW_LINES), 'L');
483 	fShowLinesMenuitem->SetMarked(true);
484 
485 	fUTF8 = new BMenuItem("UTF8", new BMessage('utf8'));
486 	fShiftJIS = new BMenuItem("ShiftJIS", new BMessage(B_SJIS_CONVERSION));
487 	fEUC = new BMenuItem("EUC", new BMessage(B_EUC_CONVERSION));
488 	fJIS = new BMenuItem("JIS", new BMessage(B_JIS_CONVERSION));
489 
490 	fFileMenu->AddItem(fNew);
491 	fFileMenu->AddSeparatorItem();
492 	fFileMenu->AddItem(fOpen);
493 	fFileMenu->AddItem(fClose);
494 	fFileMenu->AddSeparatorItem();
495 	fFileMenu->AddItem(fAbout);
496 	fFileMenu->AddSeparatorItem();
497 	fFileMenu->AddItem(fQuit);
498 
499 	fActionMenu->AddItem(fSearch);
500 	fActionMenu->AddSeparatorItem();
501 	fActionMenu->AddItem(fSelectAll);
502 	fActionMenu->AddItem(fTrimSelection);
503 	fActionMenu->AddSeparatorItem();
504 	fActionMenu->AddItem(fOpenSelection);
505 	fActionMenu->AddItem(fSelectInTracker);
506 	fActionMenu->AddItem(fCopyText);
507 
508 	fPreferencesMenu->AddItem(fRecurseLinks);
509 	fPreferencesMenu->AddItem(fRecurseDirs);
510 	fPreferencesMenu->AddItem(fSkipDotDirs);
511 	fPreferencesMenu->AddItem(fCaseSensitive);
512 	fPreferencesMenu->AddItem(fEscapeText);
513 	fPreferencesMenu->AddItem(fTextOnly);
514 	fPreferencesMenu->AddItem(fInvokePe);
515 	fPreferencesMenu->AddSeparatorItem();
516 	fPreferencesMenu->AddItem(fShowLinesMenuitem);
517 
518 	fEncodingMenu->AddItem(fUTF8);
519 	fEncodingMenu->AddItem(fShiftJIS);
520 	fEncodingMenu->AddItem(fEUC);
521 	fEncodingMenu->AddItem(fJIS);
522 
523 //	fEncodingMenu->SetLabelFromMarked(true);
524 		// Do we really want this ?
525 	fEncodingMenu->SetRadioMode(true);
526 	fEncodingMenu->ItemAt(0)->SetMarked(true);
527 
528 	fMenuBar->AddItem(fFileMenu);
529 	fMenuBar->AddItem(fActionMenu);
530 	fMenuBar->AddItem(fPreferencesMenu);
531 	fMenuBar->AddItem(fHistoryMenu);
532 	fMenuBar->AddItem(fEncodingMenu);
533 
534 	AddChild(fMenuBar);
535 	SetKeyMenuBar(fMenuBar);
536 
537 	fSearch->SetEnabled(false);
538 }
539 
540 
541 void
542 GrepWindow::_CreateViews()
543 {
544 	// The search pattern entry field does not send a message when
545 	// <Enter> is pressed, because the "Search/Cancel" button already
546 	// does this and we don't want to send the same message twice.
547 
548 	fSearchText = new BTextControl(
549 		BRect(0, 0, 0, 1), "SearchText", NULL, NULL, NULL,
550 		B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP,
551 		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_NAVIGABLE);
552 
553 	fSearchText->TextView()->SetMaxBytes(1000);
554 	fSearchText->ResizeToPreferred();
555 		// because it doesn't have a label
556 
557 	fSearchText->SetModificationMessage(new BMessage(MSG_SEARCH_TEXT));
558 
559 	fButton = new BButton(
560 		BRect(0, 1, 80, 1), "Button", B_TRANSLATE("Search"),
561 		new BMessage(MSG_START_CANCEL), B_FOLLOW_RIGHT);
562 
563 	fButton->MakeDefault(true);
564 	fButton->ResizeToPreferred();
565 	fButton->SetEnabled(false);
566 
567 	fShowLinesCheckbox = new BCheckBox(
568 		BRect(0, 0, 1, 1), "ShowLines", B_TRANSLATE("Show lines"),
569 		new BMessage(MSG_CHECKBOX_SHOW_LINES), B_FOLLOW_LEFT);
570 
571 	fShowLinesCheckbox->SetValue(B_CONTROL_ON);
572 	fShowLinesCheckbox->ResizeToPreferred();
573 
574 	fSearchResults = new GrepListView();
575 
576 	fSearchResults->SetInvocationMessage(new BMessage(MSG_INVOKE_ITEM));
577 	fSearchResults->ResizeToPreferred();
578 }
579 
580 
581 void
582 GrepWindow::_LayoutViews()
583 {
584 	float menubarWidth, menubarHeight = 20;
585 	fMenuBar->GetPreferredSize(&menubarWidth, &menubarHeight);
586 
587 	BBox *background = new BBox(
588 		BRect(0, menubarHeight + 1, 2, menubarHeight + 2), B_EMPTY_STRING,
589 		B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP,
590 		B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE_JUMP,
591 		B_PLAIN_BORDER);
592 
593 	BScrollView *scroller = new BScrollView(
594 		"ScrollSearchResults", fSearchResults, B_FOLLOW_ALL_SIDES,
595 		B_FULL_UPDATE_ON_RESIZE, true, true, B_NO_BORDER);
596 
597 	scroller->ResizeToPreferred();
598 
599 	float width = 8 + fShowLinesCheckbox->Frame().Width()
600 		+ 8 + fButton->Frame().Width() + 8;
601 
602 	float height = 8 + fSearchText->Frame().Height() + 8
603 		+ fButton->Frame().Height() + 8 + scroller->Frame().Height();
604 
605 	float backgroundHeight = 8 + fSearchText->Frame().Height()
606 		+ 8 + fButton->Frame().Height() + 8;
607 
608 	ResizeTo(width, height);
609 
610 	AddChild(background);
611 	background->ResizeTo(width,	backgroundHeight);
612 	background->AddChild(fSearchText);
613 	background->AddChild(fShowLinesCheckbox);
614 	background->AddChild(fButton);
615 
616 	fSearchText->MoveTo(8, 8);
617 	fSearchText->ResizeTo(width - 16, fSearchText->Frame().Height());
618 	fSearchText->MakeFocus(true);
619 
620 	fShowLinesCheckbox->MoveTo(
621 		8, 8 + fSearchText->Frame().Height() + 8
622 			+ (fButton->Frame().Height() - fShowLinesCheckbox->Frame().Height())/2);
623 
624 	fButton->MoveTo(
625 		width - fButton->Frame().Width() - 8,
626 		8 + fSearchText->Frame().Height() + 8);
627 
628 	AddChild(scroller);
629 	scroller->MoveTo(0, menubarHeight + 1 + backgroundHeight + 1);
630 	scroller->ResizeTo(width + 1, height - backgroundHeight - menubarHeight - 1);
631 
632 	BRect screenRect = BScreen(this).Frame();
633 
634 	MoveTo(
635 		(screenRect.Width() - width) / 2,
636 		(screenRect.Height() - height) / 2);
637 
638 	SetSizeLimits(width, 10000, height, 10000);
639 }
640 
641 
642 void
643 GrepWindow::_TileIfMultipleWindows()
644 {
645 	if (be_app->Lock()) {
646 		int32 windowCount = be_app->CountWindows();
647 		be_app->Unlock();
648 
649 		if (windowCount > 1)
650 			MoveBy(20,20);
651 	}
652 
653 	BScreen screen(this);
654 	BRect screenFrame = screen.Frame();
655 	BRect windowFrame = Frame();
656 
657 	if (windowFrame.left > screenFrame.right
658 		|| windowFrame.top > screenFrame.bottom
659 		|| windowFrame.right < screenFrame.left
660 		|| windowFrame.bottom < screenFrame.top)
661 		MoveTo(50,50);
662 }
663 
664 
665 // #pragma mark -
666 
667 
668 void
669 GrepWindow::_LoadPrefs()
670 {
671 	Lock();
672 
673 	fModel->LoadPrefs();
674 
675 	fRecurseDirs->SetMarked(fModel->fRecurseDirs);
676 	fRecurseLinks->SetMarked(fModel->fRecurseLinks);
677 	fSkipDotDirs->SetMarked(fModel->fSkipDotDirs);
678 	fCaseSensitive->SetMarked(fModel->fCaseSensitive);
679 	fEscapeText->SetMarked(fModel->fEscapeText);
680 	fTextOnly->SetMarked(fModel->fTextOnly);
681 	fInvokePe->SetMarked(fModel->fInvokePe);
682 
683 	fShowLinesCheckbox->SetValue(
684 		fModel->fShowContents ? B_CONTROL_ON : B_CONTROL_OFF);
685 	fShowLinesMenuitem->SetMarked(
686 		fModel->fShowContents ? true : false);
687 
688 	switch (fModel->fEncoding) {
689 		case 0:
690 			fUTF8->SetMarked(true);
691 			break;
692 		case B_SJIS_CONVERSION:
693 			fShiftJIS->SetMarked(true);
694 			break;
695 		case B_EUC_CONVERSION:
696 			fEUC->SetMarked(true);
697 			break;
698 		case B_JIS_CONVERSION:
699 			fJIS->SetMarked(true);
700 			break;
701 		default:
702 			printf("Woops. Bad fModel->fEncoding value.\n");
703 			break;
704 	}
705 
706 	MoveTo(fModel->fFrame.left, fModel->fFrame.top);
707 	ResizeTo(fModel->fFrame.Width(), fModel->fFrame.Height());
708 
709 	Unlock();
710 }
711 
712 
713 void
714 GrepWindow::_SavePrefs()
715 {
716 	fModel->SavePrefs();
717 }
718 
719 
720 void
721 GrepWindow::_StartNodeMonitoring()
722 {
723 	CALLED();
724 
725 	_StopNodeMonitoring();
726 
727 	BMessenger messenger(this);
728 	uint32 fileFlags = B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR;
729 
730 
731 	// watch the top level folder only, rest should be done through filtering
732 	// the node monitor notifications
733 	BPath path(&fModel->fDirectory);
734 	if (path.InitCheck() == B_OK) {
735 		TRACE_NM("start monitoring root folder: %s\n", path.Path());
736 		BPrivate::BPathMonitor::StartWatching(path.Path(),
737 			fileFlags | B_WATCH_RECURSIVELY | B_WATCH_FILES_ONLY, messenger);
738 	}
739 
740 	if (fChangesPulse == NULL) {
741 		BMessage message(MSG_NODE_MONITOR_PULSE);
742 		fChangesPulse = new BMessageRunner(BMessenger(this), &message,
743 			kChangesPulseInterval);
744 	}
745 }
746 
747 
748 void
749 GrepWindow::_StopNodeMonitoring()
750 {
751 	if (fChangesPulse == NULL)
752 		return;
753 
754 	CALLED();
755 
756 	BPrivate::BPathMonitor::StopWatching(BMessenger(this));
757 	delete fChangesIterator;
758 	fChangesIterator = NULL;
759 	delete fChangesPulse;
760 	fChangesPulse = NULL;
761 }
762 
763 
764 // #pragma mark - events
765 
766 
767 void
768 GrepWindow::_OnStartCancel()
769 {
770 	CALLED();
771 
772 	_StopNodeMonitoring();
773 
774 	if (fModel->fState == STATE_IDLE) {
775 		fSearchResults->MakeEmpty();
776 
777 		if (fSearchText->TextView()->TextLength() == 0)
778 			return;
779 
780 		fModel->fState = STATE_SEARCH;
781 
782 		fModel->AddToHistory(fSearchText->Text());
783 
784 		// From now on, we don't want to be notified when the
785 		// search pattern changes, because the control will be
786 		// displaying the names of the files we are grepping.
787 
788 		fSearchText->SetModificationMessage(NULL);
789 
790 		fFileMenu->SetEnabled(false);
791 		fActionMenu->SetEnabled(false);
792 		fPreferencesMenu->SetEnabled(false);
793 		fHistoryMenu->SetEnabled(false);
794 		fEncodingMenu->SetEnabled(false);
795 
796 		fSearchText->SetEnabled(false);
797 
798 		fButton->MakeFocus(true);
799 		fButton->SetLabel(B_TRANSLATE("Cancel"));
800 		fSearch->SetEnabled(false);
801 
802 		// We need to remember the search pattern, because during
803 		// the grepping, the text control's text will be replaced
804 		// by the name of the file that's currently being grepped.
805 		// When the grepping finishes, we need to restore the old
806 		// search pattern.
807 
808 		fOldPattern = fSearchText->Text();
809 
810 		FileIterator* iterator = new (nothrow) InitialIterator(fModel);
811 		fGrepper = new (nothrow) Grepper(fOldPattern.String(), fModel,
812 			this, iterator);
813 		if (fGrepper != NULL && fGrepper->IsValid())
814 			fGrepper->Start();
815 		else {
816 			// roll back in case of problems
817 			if (fGrepper == NULL)
818 				delete iterator;
819 			else {
820 				// Grepper owns iterator
821 				delete fGrepper;
822 				fGrepper = NULL;
823 			}
824 			fModel->fState = STATE_IDLE;
825 			// TODO: better notification to user
826 			fprintf(stderr, "Out of memory.\n");
827 		}
828 	} else if (fModel->fState == STATE_SEARCH) {
829 		fModel->fState = STATE_CANCEL;
830 		fGrepper->Cancel();
831 	}
832 }
833 
834 
835 void
836 GrepWindow::_OnSearchFinished()
837 {
838 	fModel->fState = STATE_IDLE;
839 
840 	delete fGrepper;
841 	fGrepper = NULL;
842 
843 	fFileMenu->SetEnabled(true);
844 	fActionMenu->SetEnabled(true);
845 	fPreferencesMenu->SetEnabled(true);
846 	fHistoryMenu->SetEnabled(true);
847 	fEncodingMenu->SetEnabled(true);
848 
849 	fButton->SetLabel(B_TRANSLATE("Search"));
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::_OnAboutRequested()
1475 {
1476 	BString text;
1477 	text << B_TRANSLATE(APP_NAME) << " " << "\n\n";
1478 	int32 titleLength = text.Length();
1479 	text << B_TRANSLATE("Created by Matthijs Hollemans." "\n\n"
1480 	"Contributed to by "
1481 	"Peter Hinely, Serge Fantino, Hideki Naito, Oscar Lesta, "
1482 	"Oliver Tappe, Jonas Sundström, Luc Schrijvers and"
1483 	" momoziro.\n");
1484 
1485 	BAlert* alert = new BAlert("TextSearch", text.String(), B_TRANSLATE("OK"), NULL,
1486 		NULL, B_WIDTH_AS_USUAL, B_INFO_ALERT);
1487 
1488 	BTextView* view = alert->TextView();
1489 	BFont font;
1490 	view->SetStylable(true);
1491 	view->GetFont(&font);
1492 	font.SetSize(font.Size() * 1.5);
1493 	font.SetFace(B_BOLD_FACE);
1494 	view->SetFontAndColor(0, titleLength, &font);
1495 
1496 	alert->Go(NULL);
1497 }
1498 
1499 
1500 void
1501 GrepWindow::_OnFileDrop(BMessage* message)
1502 {
1503 	if (fModel->fState != STATE_IDLE)
1504 		return;
1505 
1506 	entry_ref directory;
1507 	_InitRefsReceived(&directory, message);
1508 
1509 	fModel->fDirectory = directory;
1510 	fModel->fSelectedFiles.MakeEmpty();
1511 	fModel->fSelectedFiles = *message;
1512 
1513 	fSearchResults->MakeEmpty();
1514 
1515 	_SetWindowTitle();
1516 }
1517 
1518 
1519 void
1520 GrepWindow::_OnRefsReceived(BMessage* message)
1521 {
1522 	_OnFileDrop(message);
1523 	// It seems a B_CANCEL always follows a B_REFS_RECEIVED
1524 	// from a BFilePanel in Open mode.
1525 	//
1526 	// _OnOpenPanelCancel() is called on B_CANCEL.
1527 	// That's where saving the current dir of the file panel occurs, for now,
1528 	// and also the neccesary deletion of the file panel object.
1529 	// A hidden file panel would otherwise jam the shutdown process.
1530 }
1531 
1532 
1533 void
1534 GrepWindow::_OnOpenPanel()
1535 {
1536 	if (fFilePanel != NULL)
1537 		return;
1538 
1539 	entry_ref path;
1540 	if (get_ref_for_path(fModel->fFilePanelPath.String(), &path) != B_OK)
1541 		return;
1542 
1543 	BMessenger messenger(this);
1544 	BMessage message(MSG_REFS_RECEIVED);
1545 	fFilePanel = new BFilePanel(B_OPEN_PANEL, &messenger, &path,
1546 		B_FILE_NODE | B_DIRECTORY_NODE | B_SYMLINK_NODE, true,
1547 		&message, NULL, true, true);
1548 
1549 	fFilePanel->Show();
1550 }
1551 
1552 
1553 void
1554 GrepWindow::_OnOpenPanelCancel()
1555 {
1556 	entry_ref panelDirRef;
1557 	fFilePanel->GetPanelDirectory(&panelDirRef);
1558 	BPath path(&panelDirRef);
1559 	fModel->fFilePanelPath = path.Path();
1560 	delete fFilePanel;
1561 	fFilePanel = NULL;
1562 }
1563 
1564 
1565 void
1566 GrepWindow::_OnSelectAll(BMessage *message)
1567 {
1568 	BMessenger messenger(fSearchResults);
1569 	messenger.SendMessage(B_SELECT_ALL);
1570 }
1571 
1572 
1573 void
1574 GrepWindow::_OnNewWindow()
1575 {
1576 	BMessage cloneRefs;
1577 		// we don't want GrepWindow::InitRefsReceived()
1578 		// to mess with the refs of the current window
1579 
1580 	cloneRefs = fModel->fSelectedFiles;
1581 	cloneRefs.AddRef("dir_ref", &(fModel->fDirectory));
1582 
1583 	new GrepWindow(&cloneRefs);
1584 }
1585 
1586 
1587 // #pragma mark -
1588 
1589 
1590 void
1591 GrepWindow::_ModelChanged()
1592 {
1593 	CALLED();
1594 
1595 	_StopNodeMonitoring();
1596 	_SavePrefs();
1597 }
1598 
1599 bool
1600 GrepWindow::_OpenInPe(const entry_ref &ref, int32 lineNum)
1601 {
1602 	BMessage message('Cmdl');
1603 	message.AddRef("refs", &ref);
1604 
1605 	if (lineNum != -1)
1606 		message.AddInt32("line", lineNum);
1607 
1608 	entry_ref pe;
1609 	if (be_roster->FindApp(PE_SIGNATURE, &pe) != B_OK)
1610 		return false;
1611 
1612 	if (be_roster->IsRunning(&pe)) {
1613 		BMessenger msngr(NULL, be_roster->TeamFor(&pe));
1614 		if (msngr.SendMessage(&message) != B_OK)
1615 			return false;
1616 	} else {
1617 		if (be_roster->Launch(&pe, &message) != B_OK)
1618 			return false;
1619 	}
1620 
1621 	return true;
1622 }
1623 
1624 
1625 void
1626 GrepWindow::_RemoveFolderListDuplicates(BList* folderList)
1627 {
1628 	if (folderList == NULL)
1629 		return;
1630 
1631 	int32 folderCount = folderList->CountItems();
1632 	BString folderX;
1633 	BString folderY;
1634 
1635 	for (int32 x = 0; x < folderCount; x++) {
1636 		BPath* path = static_cast<BPath*>(folderList->ItemAt(x));
1637 		folderX = path->Path();
1638 
1639 		for (int32 y = x + 1; y < folderCount; y++) {
1640 			path = static_cast<BPath*>(folderList->ItemAt(y));
1641 			folderY = path->Path();
1642 			if (folderX == folderY) {
1643 				delete static_cast<BPath*>(folderList->RemoveItem(y));
1644 				folderCount--;
1645 				y--;
1646 			}
1647 		}
1648 	}
1649 }
1650 
1651 
1652 status_t
1653 GrepWindow::_OpenFoldersInTracker(BList* folderList)
1654 {
1655 	status_t status = B_OK;
1656 	BMessage refsMsg(B_REFS_RECEIVED);
1657 
1658 	int32 folderCount = folderList->CountItems();
1659 	for (int32 index = 0; index < folderCount; index++) {
1660 		BPath* path = static_cast<BPath*>(folderList->ItemAt(index));
1661 
1662 		entry_ref folderRef;
1663 		status = get_ref_for_path(path->Path(), &folderRef);
1664 		if (status != B_OK)
1665 			return status;
1666 
1667 		status = refsMsg.AddRef("refs", &folderRef);
1668 		if (status != B_OK)
1669 			return status;
1670 	}
1671 
1672 	status = be_roster->Launch(TRACKER_SIGNATURE, &refsMsg);
1673 	if (status != B_OK && status != B_ALREADY_RUNNING)
1674 		return status;
1675 
1676 	return B_OK;
1677 }
1678 
1679 
1680 bool
1681 GrepWindow::_AreAllFoldersOpenInTracker(BList *folderList)
1682 {
1683 	// Compare the folders we want open in Tracker to
1684 	// the actual Tracker windows currently open.
1685 
1686 	// We build a list of open Tracker windows, and compare
1687 	// it to the list of folders we want open in Tracker.
1688 
1689 	// If all folders exists in the list of Tracker windows
1690 	// return true
1691 
1692 	status_t status = B_OK;
1693 	BMessenger trackerMessenger(TRACKER_SIGNATURE);
1694 	BMessage sendMessage;
1695 	BMessage replyMessage;
1696 	BList windowList;
1697 
1698 	if (!trackerMessenger.IsValid())
1699 		return false;
1700 
1701 	for (int32 count = 1; ; count++) {
1702 		sendMessage.MakeEmpty();
1703 		replyMessage.MakeEmpty();
1704 
1705 		sendMessage.what = B_GET_PROPERTY;
1706 		sendMessage.AddSpecifier("Path");
1707 		sendMessage.AddSpecifier("Poses");
1708 		sendMessage.AddSpecifier("Window", count);
1709 
1710 		status = trackerMessenger.SendMessage(&sendMessage, &replyMessage);
1711 		if (status != B_OK)
1712 			return false;
1713 
1714 		entry_ref* trackerRef = new (nothrow) entry_ref;
1715 		status = replyMessage.FindRef("result", trackerRef);
1716 		if (status != B_OK || !windowList.AddItem(trackerRef)) {
1717 			delete trackerRef;
1718 			break;
1719 		}
1720 	}
1721 
1722 	int32 folderCount = folderList->CountItems();
1723 	int32 windowCount = windowList.CountItems();
1724 
1725 	int32 found = 0;
1726 	BPath* folderPath;
1727 	entry_ref* windowRef;
1728 	BString folderString;
1729 	BString windowString;
1730 	bool result = false;
1731 
1732 	if (folderCount > windowCount) {
1733 		// at least one folder is not open in Tracker
1734 		goto out;
1735 	}
1736 
1737 	// Loop over the two lists and see if all folders exist as window
1738 	for (int32 x = 0; x < folderCount; x++) {
1739 		for (int32 y = 0; y < windowCount; y++) {
1740 
1741 			folderPath = static_cast<BPath*>(folderList->ItemAt(x));
1742 			windowRef = static_cast<entry_ref*>(windowList.ItemAt(y));
1743 
1744 			if (folderPath == NULL)
1745 				break;
1746 
1747 			if (windowRef == NULL)
1748 				break;
1749 
1750 			folderString = folderPath->Path();
1751 
1752 			BEntry entry;
1753 			BPath path;
1754 
1755 			if (entry.SetTo(windowRef) == B_OK && path.SetTo(&entry) == B_OK) {
1756 
1757 				windowString = path.Path();
1758 
1759 				if (folderString == windowString) {
1760 					found++;
1761 					break;
1762 				}
1763 			}
1764 		}
1765 	}
1766 
1767 	result = found == folderCount;
1768 
1769 out:
1770 	// delete list of window entry_refs
1771 	for (int32 x = 0; x < windowCount; x++)
1772 		delete static_cast<entry_ref*>(windowList.ItemAt(x));
1773 
1774 	return result;
1775 }
1776 
1777 
1778 status_t
1779 GrepWindow::_SelectFilesInTracker(BList* folderList, BMessage* refsMessage)
1780 {
1781 	// loops over Tracker windows, find each windowRef,
1782 	// extract the refs that are children of windowRef,
1783 	// add refs to selection-message
1784 
1785 	status_t status = B_OK;
1786 	BMessenger trackerMessenger(TRACKER_SIGNATURE);
1787 	BMessage windowSendMessage;
1788 	BMessage windowReplyMessage;
1789 	BMessage selectionSendMessage;
1790 	BMessage selectionReplyMessage;
1791 
1792 	if (!trackerMessenger.IsValid())
1793 		return status;
1794 
1795 	// loop over Tracker windows
1796 	for (int32 windowCount = 1; ; windowCount++) {
1797 
1798 		windowSendMessage.MakeEmpty();
1799 		windowReplyMessage.MakeEmpty();
1800 
1801 		windowSendMessage.what = B_GET_PROPERTY;
1802 		windowSendMessage.AddSpecifier("Path");
1803 		windowSendMessage.AddSpecifier("Poses");
1804 		windowSendMessage.AddSpecifier("Window", windowCount);
1805 
1806 		status = trackerMessenger.SendMessage(&windowSendMessage,
1807 			&windowReplyMessage);
1808 
1809 		if (status != B_OK)
1810 			return status;
1811 
1812 		entry_ref windowRef;
1813 		status = windowReplyMessage.FindRef("result", &windowRef);
1814 		if (status != B_OK)
1815 			break;
1816 
1817 		int32 folderCount = folderList->CountItems();
1818 
1819 		// loop over folders in folderList
1820 		for (int32 x = 0; x < folderCount; x++) {
1821 			BPath* folderPath = static_cast<BPath*>(folderList->ItemAt(x));
1822 			if (folderPath == NULL)
1823 				break;
1824 
1825 			BString folderString = folderPath->Path();
1826 
1827 			BEntry windowEntry;
1828 			BPath windowPath;
1829 			BString windowString;
1830 
1831 			status = windowEntry.SetTo(&windowRef);
1832 			if (status != B_OK)
1833 				break;
1834 
1835 			status = windowPath.SetTo(&windowEntry);
1836 			if (status != B_OK)
1837 				break;
1838 
1839 			windowString = windowPath.Path();
1840 
1841 			// if match, loop over items in refsMessage
1842 			// and add those that live in window/folder
1843 			// to a selection message
1844 
1845 			if (windowString == folderString) {
1846 				selectionSendMessage.MakeEmpty();
1847 				selectionSendMessage.what = B_SET_PROPERTY;
1848 				selectionReplyMessage.MakeEmpty();
1849 
1850 				// loop over refs and add to message
1851 				entry_ref ref;
1852 				for (int32 index = 0; ; index++) {
1853 					status = refsMessage->FindRef("refs", index, &ref);
1854 					if (status != B_OK)
1855 						break;
1856 
1857 					BDirectory directory(&windowRef);
1858 					BEntry entry(&ref);
1859 					if (directory.Contains(&entry))
1860 						selectionSendMessage.AddRef("data", &ref);
1861 				}
1862 
1863 				// finish selection message
1864 				selectionSendMessage.AddSpecifier("Selection");
1865 				selectionSendMessage.AddSpecifier("Poses");
1866 				selectionSendMessage.AddSpecifier("Window", windowCount);
1867 
1868 				trackerMessenger.SendMessage(&selectionSendMessage,
1869 					&selectionReplyMessage);
1870 			}
1871 		}
1872 	}
1873 
1874 	return B_OK;
1875 }
1876