root/src/apps/text_search/GrepWindow.cpp
/*
 * Copyright (c) 1998-2007 Matthijs Hollemans
 * All rights reserved. Distributed under the terms of the MIT License.
 */
#include "GrepWindow.h"

#include <ctype.h>
#include <errno.h>
#include <new>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <Application.h>
#include <AppFileInfo.h>
#include <Alert.h>
#include <Clipboard.h>
#include <LayoutBuilder.h>
#include <MessageFilter.h>
#include <MessageRunner.h>
#include <MimeType.h>
#include <Path.h>
#include <PathMonitor.h>
#include <Roster.h>
#include <SpaceLayoutItem.h>
#include <String.h>
#include <UTF8.h>

#include "ChangesIterator.h"
#include "GlobalDefs.h"
#include "Grepper.h"
#include "InitialIterator.h"

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "GrepWindow"


const char* kAppName = B_TRANSLATE_MARK_SYSTEM_NAME("TextSearch");


using std::nothrow;

static const bigtime_t kChangesPulseInterval = 150000;

#define TRACE_NODE_MONITORING
#ifdef TRACE_NODE_MONITORING
# define TRACE_NM(x...) printf(x)
#else
# define TRACE_NM(x...)
#endif

//#define TRACE_FUNCTIONS
#ifdef TRACE_FUNCTIONS
#include <private/shared/FunctionTracer.h>

static int32 sDepth;

# define CALLED()       FunctionTracer functionTracer(printf, this, __PRETTY_FUNCTION__, sDepth)
#else
# define CALLED()
#endif // TRACE_FUNCTIONS


class HistoryInputFilter : public BMessageFilter {
public:
        HistoryInputFilter(BHandler* target)
                : BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE, B_KEY_DOWN),
                fTarget(target)
        {
        }

        virtual filter_result Filter(BMessage* message, BHandler** _target)
        {
                const char* bytes;
                int32 modifiers;
                if (message->FindString("bytes", &bytes) != B_OK)
                        return B_DISPATCH_MESSAGE;

                message->FindInt32("modifiers", &modifiers);
                if (modifiers & (B_SHIFT_KEY | B_OPTION_KEY | B_COMMAND_KEY | B_CONTROL_KEY))
                        return B_DISPATCH_MESSAGE;

                switch (bytes[0]) {
                        case B_UP_ARROW:
                        {
                                fTarget.SendMessage(new BMessage(MSG_PREV_HISTORY));
                                return B_SKIP_MESSAGE;
                        } break;

                        case B_DOWN_ARROW:
                        {
                                fTarget.SendMessage(new BMessage(MSG_NEXT_HISTORY));
                                return B_SKIP_MESSAGE;
                        } break;

                        default:
                                return B_DISPATCH_MESSAGE;
                }
        }

        private:
                BMessenger      fTarget;
};


GrepWindow::GrepWindow(BMessage* message)
        : BWindow(BRect(0, 0, 525, 430), NULL, B_DOCUMENT_WINDOW,
                B_AUTO_UPDATE_SIZE_LIMITS),
        fSearchText(NULL),
        fSearchResults(NULL),
        fMenuBar(NULL),
        fFileMenu(NULL),
        fNew(NULL),
        fOpen(NULL),
        fClose(NULL),
        fQuit(NULL),
        fActionMenu(NULL),
        fSelectAll(NULL),
        fSearch(NULL),
        fTrimSelection(NULL),
        fCopyText(NULL),
        fSelectInTracker(NULL),
        fOpenSelection(NULL),
        fPreferencesMenu(NULL),
        fRecurseLinks(NULL),
        fRecurseDirs(NULL),
        fSkipDotDirs(NULL),
        fCaseSensitive(NULL),
        fRegularExpression(NULL),
        fTextOnly(NULL),
        fInvokeEditor(NULL),
        fHistoryMenu(NULL),
        fEncodingMenu(NULL),
        fUTF8(NULL),
        fShiftJIS(NULL),
        fEUC(NULL),
        fJIS(NULL),

        fShowLinesCheckbox(NULL),
        fButton(NULL),

        fGrepper(NULL),
        fOldPattern(""),
        fIncludeFilesGlob(""),
        fModel(new (nothrow) Model()),
        fLastNodeMonitorEvent(system_time()),
        fChangesIterator(NULL),
        fChangesPulse(NULL),

        fCurrentHistoryIndex(-1),
        fFilePanel(NULL)
{
        if (fModel == NULL)
                return;

        entry_ref directory;
        _InitRefsReceived(&directory, message);

        fModel->fDirectory = directory;
        fModel->fSelectedFiles = *message;

        _SetWindowTitle();
        _CreateMenus();
        _UpdateMenus();
        _CreateViews();
        _LayoutViews();
        _LoadPrefs();
        _TileIfMultipleWindows();

        Show();
}


GrepWindow::~GrepWindow()
{
        delete fGrepper;
        delete fModel;
}


void GrepWindow::FrameResized(float width, float height)
{
        BWindow::FrameResized(width, height);
        fModel->fFrame = Frame();
        _SavePrefs();
}


void GrepWindow::FrameMoved(BPoint origin)
{
        BWindow::FrameMoved(origin);
        fModel->fFrame = Frame();
        _SavePrefs();
}


void GrepWindow::MenusBeginning()
{
        fModel->FillHistoryMenu(fHistoryMenu);

        if (fHistoryMenu->CountItems() > 0) {
                fHistoryMenu->AddSeparatorItem();
                BMessage* message = new BMessage(MSG_CLEAR_HISTORY);
                fHistoryMenu->AddItem(new BMenuItem(B_TRANSLATE("Clear history"), message));
        }

        BWindow::MenusBeginning();
}


void GrepWindow::MenusEnded()
{
        for (int32 t = fHistoryMenu->CountItems(); t > 0; --t)
                delete fHistoryMenu->RemoveItem(t - 1);

        BWindow::MenusEnded();
}


void GrepWindow::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case MSG_NEW_WINDOW:
                        _OnNewWindow();
                        break;

                case B_SIMPLE_DATA:
                        _OnFileDrop(message);
                        break;

                case MSG_OPEN_PANEL:
                        _OnOpenPanel();
                        break;

                case MSG_REFS_RECEIVED:
                        _OnRefsReceived(message);
                        break;

                case MSG_SET_TARGET_TO_PARENT:
                        _OnSetTargetToParent();
                        break;

                case B_CANCEL:
                        _OnOpenPanelCancel();
                        break;

                case MSG_RECURSE_LINKS:
                        _OnRecurseLinks();
                        break;

                case MSG_RECURSE_DIRS:
                        _OnRecurseDirs();
                        break;

                case MSG_SKIP_DOT_DIRS:
                        _OnSkipDotDirs();
                        break;

                case MSG_CASE_SENSITIVE:
                        _OnCaseSensitive();
                        break;

                case MSG_REGULAR_EXPRESSION:
                        _OnRegularExpression();
                        break;

                case MSG_TEXT_ONLY:
                        _OnTextOnly();
                        break;

                case MSG_INVOKE_EDITOR:
                        _OnInvokeEditor();
                        break;

                case MSG_SEARCH_TEXT:
                        fCurrentHistoryIndex = -1;      // reset on user input
                        _OnSearchText();
                        break;

                case MSG_SEARCH_GLOB_FILTER:
                        _OnGlobFilterChange();
                        break;

                case MSG_SELECT_HISTORY:
                        _OnHistoryItem(message);
                        break;

                case MSG_PREV_HISTORY:
                {
                        if (fCurrentHistoryIndex == HISTORY_LIMIT - 1)
                                break;

                        fCurrentHistoryIndex++;
                        BString text = fModel->GetHistoryItem(fCurrentHistoryIndex);
                        if (text != NULL) {
                                fSearchText->SetModificationMessage(NULL);
                                fSearchText->SetText(text);
                                fSearchText->SetModificationMessage(new BMessage(MSG_SEARCH_TEXT));
                        } else
                                fCurrentHistoryIndex--;

                        _OnSearchText();
                        break;
                }
                case MSG_NEXT_HISTORY:
                {
                        if (fCurrentHistoryIndex <= 0) {
                                fCurrentHistoryIndex = -1;
                                fSearchText->SetText("");
                                _OnSearchText();
                                break;
                        }

                        fCurrentHistoryIndex--;
                        BString text = fModel->GetHistoryItem(fCurrentHistoryIndex);
                        if (text != NULL) {
                                fSearchText->SetModificationMessage(NULL);
                                fSearchText->SetText(text);
                                fSearchText->SetModificationMessage(new BMessage(MSG_SEARCH_TEXT));
                        } else {
                                fCurrentHistoryIndex--;
                                fSearchText->SetText("");
                        }

                        _OnSearchText();
                        break;
                }

                case MSG_CLEAR_HISTORY:
                        fModel->ClearHistory();
                        break;

                case MSG_START_CANCEL:
                        _OnStartCancel();
                        break;

                case MSG_SEARCH_FINISHED:
                        _OnSearchFinished();
                        break;

                case MSG_START_NODE_MONITORING:
                        _StartNodeMonitoring();
                        break;

                case B_PATH_MONITOR:
                        _OnNodeMonitorEvent(message);
                        break;

                case MSG_NODE_MONITOR_PULSE:
                        _OnNodeMonitorPulse();
                        break;

                case MSG_REPORT_FILE_NAME:
                        _OnReportFileName(message);
                        break;

                case MSG_REPORT_RESULT:
                        _OnReportResult(message);
                        break;

                case MSG_REPORT_ERROR:
                        _OnReportError(message);
                        break;

                case MSG_SELECT_ALL:
                        _OnSelectAll(message);
                        break;

                case MSG_TRIM_SELECTION:
                        _OnTrimSelection();
                        break;

                case MSG_COPY_TEXT:
                        _OnCopyText();
                        break;

                case MSG_SELECT_IN_TRACKER:
                        _OnSelectInTracker();
                        break;

                case MSG_CHECKBOX_SHOW_LINES:
                        _OnCheckboxShowLines();
                        break;

                case MSG_OPEN_SELECTION:
                        // fall through
                case MSG_INVOKE_ITEM:
                        _OnInvokeItem();
                        break;

                case MSG_QUIT_NOW:
                        _OnQuitNow();
                        break;

                case 'utf8':
                        fModel->fEncoding = 0;
                        break;

                case B_SJIS_CONVERSION:
                        fModel->fEncoding = B_SJIS_CONVERSION;
                        break;

                case B_EUC_CONVERSION:
                        fModel->fEncoding = B_EUC_CONVERSION;
                        break;

                case B_JIS_CONVERSION:
                        fModel->fEncoding = B_JIS_CONVERSION;
                        break;

                default:
                        BWindow::MessageReceived(message);
                        break;
        }
}


void
GrepWindow::Quit()
{
        CALLED();

        _StopNodeMonitoring();
        _SavePrefs();

        // TODO: stippi: Looks like this could be done
        // by maintaining a counter in GrepApp with the number of open
        // grep windows... and just quit when it goes zero
        if (be_app->Lock()) {
                be_app->PostMessage(MSG_TRY_QUIT);
                be_app->Unlock();
                BWindow::Quit();
        }
}


// #pragma mark -


void
GrepWindow::_InitRefsReceived(entry_ref* directory, BMessage* message)
{
        // HACK-HACK-HACK:
        // If the user selected a single folder and invoked TextSearch on it,
        // but recurse directories is switched off, TextSearch would do nothing.
        // In that special case, we'd like it to recurse into that folder (but
        // not go any deeper after that).

        type_code code;
        int32 count;
        message->GetInfo("refs", &code, &count);

        if (count == 0) {
                if (message->FindRef("dir_ref", 0, directory) == B_OK)
                        message->MakeEmpty();
        }

        if (count == 1) {
                entry_ref ref;
                if (message->FindRef("refs", 0, &ref) == B_OK) {
                        BEntry entry(&ref, true);
                        if (entry.IsDirectory()) {
                                // ok, special case, we use this folder as base directory
                                // and pretend nothing had been selected:
                                *directory = ref;
                                message->MakeEmpty();
                        }
                }
        }
}


void
GrepWindow::_SetWindowTitle()
{
        BEntry entry(&fModel->fDirectory, true);
        BString title;
        if (entry.InitCheck() == B_OK) {
                BPath path;
                if (entry.GetPath(&path) == B_OK) {
                        if (fOldPattern.Length()) {
                                title = B_TRANSLATE("%appname% : %path% : %searchtext%");
                                title.ReplaceAll("%searchtext%", fOldPattern.String());
                        } else
                                title = B_TRANSLATE("%appname% : %path%");

                        title.ReplaceAll("%appname%", B_TRANSLATE_NOCOLLECT(kAppName));
                        title.ReplaceAll("%path%", path.Path());
                }
        }

        if (!title.Length())
                title = B_TRANSLATE_NOCOLLECT(kAppName);

        SetTitle(title.String());
}


void
GrepWindow::_CreateMenus()
{
        fMenuBar = new BMenuBar("menubar");

        fFileMenu = new BMenu(B_TRANSLATE("File"));
        fActionMenu = new BMenu(B_TRANSLATE("Actions"));
        fPreferencesMenu = new BMenu(B_TRANSLATE("Settings"));
        fHistoryMenu = new BMenu(B_TRANSLATE("History"));
        fEncodingMenu = new BMenu(B_TRANSLATE("Encoding"));

        fNew = new BMenuItem(
                B_TRANSLATE("New window"), new BMessage(MSG_NEW_WINDOW), 'N');

        fOpen = new BMenuItem(
                B_TRANSLATE("Set target" B_UTF8_ELLIPSIS), new BMessage(MSG_OPEN_PANEL), 'F');

        fSetTargetToParent = new BMenuItem(
                B_TRANSLATE("Set target to parent folder"),
                new BMessage(MSG_SET_TARGET_TO_PARENT), B_UP_ARROW);

        fClose = new BMenuItem(
                B_TRANSLATE("Close"), new BMessage(B_QUIT_REQUESTED), 'W');

        fQuit = new BMenuItem(
                B_TRANSLATE("Quit"), new BMessage(MSG_QUIT_NOW), 'Q');

        fSearch = new BMenuItem(
                B_TRANSLATE("Search"), new BMessage(MSG_START_CANCEL), 'S');

        fSelectAll = new BMenuItem(
                B_TRANSLATE("Select all"), new BMessage(MSG_SELECT_ALL),
                'A', B_SHIFT_KEY);

        fTrimSelection = new BMenuItem(
                B_TRANSLATE("Trim to selection"), new BMessage(MSG_TRIM_SELECTION), 'T');

        fOpenSelection = new BMenuItem(
                B_TRANSLATE("Open selection"), new BMessage(MSG_OPEN_SELECTION), 'O');

        fSelectInTracker = new BMenuItem(
                B_TRANSLATE("Show files in Tracker"),
                        new BMessage(MSG_SELECT_IN_TRACKER), 'K');

        fCopyText = new BMenuItem(
                B_TRANSLATE("Copy text to clipboard"), new BMessage(MSG_COPY_TEXT), 'B');

        fRecurseLinks = new BMenuItem(
                B_TRANSLATE("Follow symbolic links"), new BMessage(MSG_RECURSE_LINKS));

        fRecurseDirs = new BMenuItem(
                B_TRANSLATE("Look in sub-folders"), new BMessage(MSG_RECURSE_DIRS));

        fSkipDotDirs = new BMenuItem(
                B_TRANSLATE("Skip folders starting with a dot"),
                        new BMessage(MSG_SKIP_DOT_DIRS));

        fCaseSensitive = new BMenuItem(
                B_TRANSLATE("Case-sensitive"), new BMessage(MSG_CASE_SENSITIVE));

        fRegularExpression = new BMenuItem(
                B_TRANSLATE("Regular expression"), new BMessage(MSG_REGULAR_EXPRESSION));

        fTextOnly = new BMenuItem(
                B_TRANSLATE("Text files only"), new BMessage(MSG_TEXT_ONLY));

        fInvokeEditor = new BMenuItem(
                B_TRANSLATE("Open files in code editor"), new BMessage(MSG_INVOKE_EDITOR));

        fUTF8 = new BMenuItem("UTF8", new BMessage('utf8'));
        fShiftJIS = new BMenuItem("ShiftJIS", new BMessage(B_SJIS_CONVERSION));
        fEUC = new BMenuItem("EUC", new BMessage(B_EUC_CONVERSION));
        fJIS = new BMenuItem("JIS", new BMessage(B_JIS_CONVERSION));

        fFileMenu->AddItem(fNew);
        fFileMenu->AddSeparatorItem();
        fFileMenu->AddItem(fOpen);
        fFileMenu->AddItem(fSetTargetToParent);
        fFileMenu->AddItem(fClose);
        fFileMenu->AddSeparatorItem();
        fFileMenu->AddItem(fQuit);

        fActionMenu->AddItem(fSearch);
        fActionMenu->AddSeparatorItem();
        fActionMenu->AddItem(fSelectAll);
        fActionMenu->AddItem(fTrimSelection);
        fActionMenu->AddSeparatorItem();
        fActionMenu->AddItem(fOpenSelection);
        fActionMenu->AddItem(fSelectInTracker);
        fActionMenu->AddItem(fCopyText);

        fPreferencesMenu->AddItem(fRecurseLinks);
        fPreferencesMenu->AddItem(fRecurseDirs);
        fPreferencesMenu->AddItem(fSkipDotDirs);
        fPreferencesMenu->AddItem(fCaseSensitive);
        fPreferencesMenu->AddItem(fRegularExpression);
        fPreferencesMenu->AddItem(fTextOnly);
        fPreferencesMenu->AddItem(fInvokeEditor);

        fEncodingMenu->AddItem(fUTF8);
        fEncodingMenu->AddItem(fShiftJIS);
        fEncodingMenu->AddItem(fEUC);
        fEncodingMenu->AddItem(fJIS);

//      fEncodingMenu->SetLabelFromMarked(true);
                // Do we really want this ?
        fEncodingMenu->SetRadioMode(true);
        fEncodingMenu->ItemAt(0)->SetMarked(true);

        fMenuBar->AddItem(fFileMenu);
        fMenuBar->AddItem(fActionMenu);
        fMenuBar->AddItem(fPreferencesMenu);
        fMenuBar->AddItem(fHistoryMenu);
        fMenuBar->AddItem(fEncodingMenu);

        fSearch->SetEnabled(false);
}


void
GrepWindow::_UpdateMenus()
{
        bool targetIsSingleDirectory =
                BEntry(&(fModel->fDirectory)).InitCheck() == B_OK;
        fSetTargetToParent->SetEnabled(targetIsSingleDirectory);
}


void
GrepWindow::_CreateViews()
{
        // The search pattern entry field does not send a message when
        // <Enter> is pressed, because the "Search/Cancel" button already
        // does this and we don't want to send the same message twice.

        fSearchText = new BTextControl(
                "SearchText", NULL, NULL, NULL,
                B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_NAVIGABLE);

        fSearchText->TextView()->AddFilter(new HistoryInputFilter(this));
        fSearchText->TextView()->SetMaxBytes(1000);
        fSearchText->SetModificationMessage(new BMessage(MSG_SEARCH_TEXT));

        fGlobText = new BTextControl(
                "GlobText", B_TRANSLATE("File filter:"), NULL, NULL,
                B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_NAVIGABLE);

        fGlobText->TextView()->SetMaxBytes(20);
        fGlobText->SetModificationMessage(new BMessage(MSG_SEARCH_GLOB_FILTER));
        fGlobText->SetToolTip(B_TRANSLATE("Only search files matching the given pattern, e.g. \"*.h\"."));

        fButton = new BButton(
                "Button", B_TRANSLATE("Search"),
                new BMessage(MSG_START_CANCEL));
        fButton->MakeDefault(true);
        fButton->SetEnabled(false);

        fShowLinesCheckbox = new BCheckBox(
                "ShowLines", B_TRANSLATE("Show lines"),
                new BMessage(MSG_CHECKBOX_SHOW_LINES));
        fShowLinesCheckbox->SetValue(B_CONTROL_ON);

        fSearchResults = new GrepListView();
        fSearchResults->SetInvocationMessage(new BMessage(MSG_INVOKE_ITEM));
}


void
GrepWindow::_LayoutViews()
{
        BScrollView* scroller = new BScrollView(
                "ScrollSearchResults", fSearchResults,
                B_FULL_UPDATE_ON_RESIZE, true, true);

        BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
                .SetInsets(0, 0, -1, -1)
                .Add(fMenuBar)
                .AddGroup(B_VERTICAL, B_USE_SMALL_SPACING)
                        .SetInsets(B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING,
                                B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING)
                        .Add(fSearchText)
                        .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING)
                                .Add(fGlobText, 1.0)
                                .Add(fShowLinesCheckbox)
                                .AddGlue(0.5)
                                .Add(fButton)
                        .End()
                .End()
                .AddGroup(B_VERTICAL, 0)
                        .SetInsets(-2, 0, -1, -1)
                        .Add(scroller)
                .End()
        .End();

        fSearchText->MakeFocus(true);

        SetKeyMenuBar(fMenuBar);
}


void
GrepWindow::_TileIfMultipleWindows()
{
        if (be_app->Lock()) {
                int32 windowCount = be_app->CountWindows();
                be_app->Unlock();

                if (windowCount > 1)
                        MoveBy(20, 20);
        }

        BScreen screen(this);
        BRect screenFrame = screen.Frame();
        BRect windowFrame = Frame();

        if (windowFrame.left > screenFrame.right
                || windowFrame.top > screenFrame.bottom
                || windowFrame.right < screenFrame.left
                || windowFrame.bottom < screenFrame.top)
                MoveTo(50, 50);
}


// #pragma mark -


void
GrepWindow::_LoadPrefs()
{
        Lock();

        fModel->LoadPrefs();

        fRecurseDirs->SetMarked(fModel->fRecurseDirs);
        fRecurseLinks->SetMarked(fModel->fRecurseLinks);
        fSkipDotDirs->SetMarked(fModel->fSkipDotDirs);
        fCaseSensitive->SetMarked(fModel->fCaseSensitive);
        fRegularExpression->SetMarked(fModel->fRegularExpression);
        fTextOnly->SetMarked(fModel->fTextOnly);
        fInvokeEditor->SetMarked(fModel->fInvokeEditor);

        fShowLinesCheckbox->SetValue(fModel->fShowLines);

        switch (fModel->fEncoding) {
                case 0:
                        fUTF8->SetMarked(true);
                        break;
                case B_SJIS_CONVERSION:
                        fShiftJIS->SetMarked(true);
                        break;
                case B_EUC_CONVERSION:
                        fEUC->SetMarked(true);
                        break;
                case B_JIS_CONVERSION:
                        fJIS->SetMarked(true);
                        break;
                default:
                        printf("Woops. Bad fModel->fEncoding value.\n");
                        break;
        }

        MoveTo(fModel->fFrame.left, fModel->fFrame.top);
        ResizeTo(fModel->fFrame.Width(), fModel->fFrame.Height());

        Unlock();
}


void
GrepWindow::_SavePrefs()
{
        fModel->SavePrefs();
}


void
GrepWindow::_StartNodeMonitoring()
{
        CALLED();

        _StopNodeMonitoring();

        BMessenger messenger(this);
        uint32 fileFlags = B_WATCH_NAME | B_WATCH_STAT;


        // watch the top level folder only, rest should be done through filtering
        // the node monitor notifications
        BPath path(&fModel->fDirectory);
        if (path.InitCheck() == B_OK) {
                TRACE_NM("start monitoring root folder: %s\n", path.Path());
                BPrivate::BPathMonitor::StartWatching(path.Path(),
                        fileFlags | B_WATCH_RECURSIVELY | B_WATCH_FILES_ONLY, messenger);
        }

        if (fChangesPulse == NULL) {
                BMessage message(MSG_NODE_MONITOR_PULSE);
                fChangesPulse = new BMessageRunner(BMessenger(this), &message,
                        kChangesPulseInterval);
        }
}


void
GrepWindow::_StopNodeMonitoring()
{
        if (fChangesPulse == NULL)
                return;

        CALLED();

        BPrivate::BPathMonitor::StopWatching(BMessenger(this));
        delete fChangesIterator;
        fChangesIterator = NULL;
        delete fChangesPulse;
        fChangesPulse = NULL;
}


// #pragma mark - events


void
GrepWindow::_OnStartCancel()
{
        CALLED();

        _StopNodeMonitoring();

        if (fModel->fState == STATE_IDLE) {
                fSearchResults->MakeEmpty();

                if (fSearchText->TextView()->TextLength() == 0)
                        return;

                fModel->fState = STATE_SEARCH;

                fModel->AddToHistory(fSearchText->Text());

                // From now on, we don't want to be notified when the
                // search pattern changes, because the control will be
                // displaying the names of the files we are grepping.

                fSearchText->SetModificationMessage(NULL);
                fGlobText->SetModificationMessage(NULL);
                fCurrentHistoryIndex = -1;

                fFileMenu->SetEnabled(false);
                fActionMenu->SetEnabled(false);
                fPreferencesMenu->SetEnabled(false);
                fHistoryMenu->SetEnabled(false);
                fEncodingMenu->SetEnabled(false);

                fSearchText->SetEnabled(false);
                fGlobText->SetEnabled(false);

                fButton->MakeFocus(true);
                fButton->SetLabel(B_TRANSLATE("Cancel"));

                fSearch->SetEnabled(false);

                // We need to remember the search pattern, because during
                // the grepping, the text control's text will be replaced
                // by the name of the file that's currently being grepped.
                // When the grepping finishes, we need to restore the old
                // search pattern.

                fOldPattern = fSearchText->Text();

                _SetWindowTitle();

                FileIterator* iterator = new (nothrow) InitialIterator(fModel);
                fGrepper = new (nothrow) Grepper(fOldPattern.String(), fIncludeFilesGlob.String(), fModel,
                        this, iterator);
                if (fGrepper != NULL && fGrepper->IsValid())
                        fGrepper->Start();
                else {
                        // roll back in case of problems
                        if (fGrepper == NULL)
                                delete iterator;
                        else {
                                // Grepper owns iterator
                                delete fGrepper;
                                fGrepper = NULL;
                        }
                        fModel->fState = STATE_IDLE;
                        // TODO: better notification to user
                        fprintf(stderr, "Out of memory.\n");
                }
        } else if (fModel->fState == STATE_SEARCH) {
                fModel->fState = STATE_CANCEL;
                fGrepper->Cancel();
        }
}


void
GrepWindow::_OnSearchFinished()
{
        fModel->fState = STATE_IDLE;

        delete fGrepper;
        fGrepper = NULL;

        fFileMenu->SetEnabled(true);
        fActionMenu->SetEnabled(true);
        fPreferencesMenu->SetEnabled(true);
        fHistoryMenu->SetEnabled(true);
        fEncodingMenu->SetEnabled(true);

        fButton->SetLabel(B_TRANSLATE("Search"));

        fButton->SetEnabled(true);
        fSearch->SetEnabled(true);

        fSearchText->SetEnabled(true);
        fSearchText->MakeFocus(true);
        fSearchText->SetText(fOldPattern.String());
        fSearchText->TextView()->SelectAll();
        fSearchText->SetModificationMessage(new BMessage(MSG_SEARCH_TEXT));
        fGlobText->SetEnabled(true);
        fGlobText->SetModificationMessage(new BMessage(MSG_SEARCH_GLOB_FILTER));

        PostMessage(MSG_START_NODE_MONITORING);
}


void
GrepWindow::_OnNodeMonitorEvent(BMessage* message)
{
        int32 opCode;
        if (message->FindInt32("opcode", &opCode) != B_OK)
                return;

        if (fChangesIterator == NULL) {
                fChangesIterator = new (nothrow) ChangesIterator(fModel);
                if (fChangesIterator == NULL || !fChangesIterator->IsValid()) {
                        delete fChangesIterator;
                        fChangesIterator = NULL;
                }
        }

        switch (opCode) {
                case B_ENTRY_CREATED:
                case B_ENTRY_REMOVED:
                {
                        TRACE_NM("%s\n", opCode == B_ENTRY_CREATED ? "B_ENTRY_CREATED"
                                : "B_ENTRY_REMOVED");
                        BString path;
                        if (message->FindString("path", &path) == B_OK) {
                                if (opCode == B_ENTRY_CREATED) {
                                        if (fChangesIterator != NULL)
                                                fChangesIterator->EntryAdded(path.String());
                                } else {
                                        // in order to remove temporary files
                                        if (fChangesIterator != NULL)
                                                fChangesIterator->EntryRemoved(path.String());
                                        // remove from the list view already
                                        BEntry entry(path.String());
                                        entry_ref ref;
                                        if (entry.GetRef(&ref) == B_OK)
                                                fSearchResults->RemoveResults(ref, true);
                                }
                        } else {
                                #ifdef TRACE_NODE_MONITORING
                                        printf("incompatible message:\n");
                                        message->PrintToStream();
                                #endif
                        }
                        TRACE_NM("path: %s\n", path.String());
                        break;
                }
                case B_ENTRY_MOVED:
                {
                        TRACE_NM("B_ENTRY_MOVED\n");

                        BString path;
                        if (message->FindString("path", &path) != B_OK) {
                                #ifdef TRACE_NODE_MONITORING
                                        printf("incompatible message:\n");
                                        message->PrintToStream();
                                #endif
                                break;
                        }

                        bool added;
                        if (message->FindBool("added", &added) != B_OK)
                                added = false;
                        bool removed;
                        if (message->FindBool("removed", &removed) != B_OK)
                                removed = false;

                        if (added) {
                                // new files
                        } else if (removed) {
                                // remove files
                        } else {
                                // files changed location, but are still within the search
                                // path!
                                BEntry entry(path.String());
                                entry_ref ref;
                                if (entry.GetRef(&ref) == B_OK) {
                                        int32 index;
                                        ResultItem* item = fSearchResults->FindItem(ref, &index);
                                        if (item != NULL) {
                                                item->SetText(path.String());
                                                // take care of invalidation, the index is currently
                                                // the full list index, but needs to be the visible
                                                // items index for this
                                                index = fSearchResults->IndexOf(item);
                                                fSearchResults->InvalidateItem(index);
                                        }
                                }
                        }
                        break;
                }
                case B_STAT_CHANGED:
                {
                        int32 fields;
                        message->FindInt32("fields", &fields);

                        TRACE_NM("B_STAT_CHANGED (fields = 0x%" B_PRIx32 ")\n", fields);

                        // No point in triggering a new search if this was the only change.
                        if (fields == B_STAT_CHANGE_TIME)
                                break;

                        // For directly watched files, the path will include the
                        // name. When the event occurs for a file in a watched directory,
                        // the message will have an extra name field for the respective
                        // file.
                        BString path;
                        if (message->FindString("path", &path) == B_OK) {
                                if (fChangesIterator != NULL)
                                        fChangesIterator->EntryChanged(path.String());
                        } else {
                                #ifdef TRACE_NODE_MONITORING
                                        printf("incompatible message:\n");
                                        message->PrintToStream();
                                #endif
                        }
                        TRACE_NM("path: %s\n", path.String());
// message->PrintToStream();
                        break;
                }

                default:
                        TRACE_NM("unkown op code\n");
                        break;
        }

        fLastNodeMonitorEvent = system_time();
}


void
GrepWindow::_OnNodeMonitorPulse()
{
        if (fChangesIterator == NULL || fChangesIterator->IsEmpty())
                return;

        if (system_time() - fLastNodeMonitorEvent < kChangesPulseInterval) {
                // wait for things to settle down before running the search for changes
                return;
        }

        if (fModel->fState != STATE_IDLE) {
                // An update or search is still in progress. New node monitor messages
                // may arrive while an update is still running. They should not arrive
                // during a regular search, but we want to be prepared for that anyways
                // and check != STATE_IDLE.
                return;
        }

        fOldPattern = fSearchText->Text();

#ifdef TRACE_NODE_MONITORING
        fChangesIterator->PrintToStream();
#endif

        fGrepper = new (nothrow) Grepper(fOldPattern.String(), fIncludeFilesGlob.String(), fModel,
                this, fChangesIterator);
        if (fGrepper != NULL && fGrepper->IsValid()) {
                fGrepper->Start();
                fChangesIterator = NULL;
                fModel->fState = STATE_UPDATE;
        } else {
                // roll back in case of problems
                if (fGrepper == NULL)
                        delete fChangesIterator;
                else {
                        // Grepper owns iterator
                        delete fGrepper;
                        fGrepper = NULL;
                }
                fprintf(stderr, "Out of memory.\n");
        }
}


void
GrepWindow::_OnReportFileName(BMessage* message)
{
        if (fModel->fState != STATE_UPDATE) {
                BString name = message->FindString("filename");
                fSearchText->TruncateString(&name, B_TRUNCATE_MIDDLE,
                        fSearchText->Bounds().Width() - 10);

                fSearchText->SetText(name);
        }
}


void
GrepWindow::_OnReportResult(BMessage* message)
{
        CALLED();
        entry_ref ref;
        if (message->FindRef("ref", &ref) != B_OK)
                return;

        type_code type;
        int32 count;
        message->GetInfo("text", &type, &count);

        BStringItem* item = NULL;
        if (fModel->fState == STATE_UPDATE) {
                // During updates because of node monitor events, negatives are
                // also reported (count == 0).
                item = fSearchResults->RemoveResults(ref, count == 0);
        }

        if (count == 0)
                return;

        if (item == NULL) {
                item = new ResultItem(ref);
                fSearchResults->AddItem(item);
                item->SetExpanded(fShowLinesCheckbox->Value() == 1);
        }

        const char* buf;
        while (message->FindString("text", --count, &buf) == B_OK) {
                uchar* temp = (uchar*)strdup(buf);
                uchar* ptr = temp;

                while (true) {
                        // replace all non-printable characters by spaces
                        uchar c = *ptr;

                        if (c == '\0')
                                break;

                        if (!(c & 0x80) && iscntrl(c))
                                *ptr = ' ';

                        ++ptr;
                }

                fSearchResults->AddUnder(new BStringItem((const char*)temp), item);

                free(temp);
        }
}


void
GrepWindow::_OnReportError(BMessage* message)
{
        const char* buf;
        if (message->FindString("error", &buf) == B_OK)
                fSearchResults->AddItem(new BStringItem(buf));
}


void
GrepWindow::_OnRecurseLinks()
{
        fModel->fRecurseLinks = !fModel->fRecurseLinks;
        fRecurseLinks->SetMarked(fModel->fRecurseLinks);
        _ModelChanged();
}


void
GrepWindow::_OnRecurseDirs()
{
        fModel->fRecurseDirs = !fModel->fRecurseDirs;
        fRecurseDirs->SetMarked(fModel->fRecurseDirs);
        _ModelChanged();
}


void
GrepWindow::_OnSkipDotDirs()
{
        fModel->fSkipDotDirs = !fModel->fSkipDotDirs;
        fSkipDotDirs->SetMarked(fModel->fSkipDotDirs);
        _ModelChanged();
}


void
GrepWindow::_OnRegularExpression()
{
        fModel->fRegularExpression = !fModel->fRegularExpression;
        fRegularExpression->SetMarked(fModel->fRegularExpression);
        _ModelChanged();
}


void
GrepWindow::_OnCaseSensitive()
{
        fModel->fCaseSensitive = !fModel->fCaseSensitive;
        fCaseSensitive->SetMarked(fModel->fCaseSensitive);
        _ModelChanged();
}


void
GrepWindow::_OnTextOnly()
{
        fModel->fTextOnly = !fModel->fTextOnly;
        fTextOnly->SetMarked(fModel->fTextOnly);
        _ModelChanged();
}


void
GrepWindow::_OnInvokeEditor()
{
        fModel->fInvokeEditor = !fModel->fInvokeEditor;
        fInvokeEditor->SetMarked(fModel->fInvokeEditor);
        _SavePrefs();
}


void
GrepWindow::_OnCheckboxShowLines()
{
        // Selection in BOutlineListView in multiple selection mode
        // gets weird when collapsing. I've tried all sorts of things.
        // It seems impossible to make it behave just right.

        // Going from collapsed to expande mode, the superitems
        // keep their selection, the subitems don't (yet) have
        // a selection. This works as expected, AFAIK.

        // Going from expanded to collapsed mode, I would like
        // for a selected subitem (line) to select its superitem,
        // (its file) and the subitem be unselected.

        // I've successfully tried code patches that apply the
        // selection pattern that I want, but with weird effects
        // on subsequent manual selection.
        // Lines stay selected while the user tries to select
        // some other line. It just gets weird.

        // It's as though listItem->Select() and Deselect()
        // put the items in some semi-selected state.
        // Or maybe I've got it all wrong.

        // So, here's the plain basic collapse/expand.
        // I think it's the least bad of what's possible on BeOS R5,
        // but perhaps someone comes along with a patch of magic.

        fModel->fShowLines = (fShowLinesCheckbox->Value() == 1);

        int32 numItems = fSearchResults->FullListCountItems();
        for (int32 x = 0; x < numItems; ++x) {
                BListItem* listItem = fSearchResults->FullListItemAt(x);
                if (listItem->OutlineLevel() == 0) {
                        if (fModel->fShowLines) {
                                if (!fSearchResults->IsExpanded(x))
                                        fSearchResults->Expand(listItem);
                        } else {
                                if (fSearchResults->IsExpanded(x))
                                        fSearchResults->Collapse(listItem);
                        }
                }
        }

        fSearchResults->Invalidate();

        _SavePrefs();
}


void
GrepWindow::_OnInvokeItem()
{
        for (int32 selectionIndex = 0; ; selectionIndex++) {
                int32 itemIndex = fSearchResults->CurrentSelection(selectionIndex);
                BListItem* item = fSearchResults->ItemAt(itemIndex);
                if (item == NULL)
                        break;

                int32 level = item->OutlineLevel();
                int32 lineNum = -1;

                // Get the line number.
                // only this level has line numbers
                if (level == 1) {
                        BStringItem* str = dynamic_cast<BStringItem*>(item);
                        if (str != NULL) {
                                lineNum = atol(str->Text());
                                        // fortunately, atol knows when to stop the conversion
                        }
                }

                // Get the top-most item and launch its entry_ref.
                while (level != 0) {
                        item = fSearchResults->Superitem(item);
                        if (item == NULL)
                                break;
                        level = item->OutlineLevel();
                }

                ResultItem* entry = dynamic_cast<ResultItem*>(item);
                if (entry != NULL) {
                        if (fModel->fInvokeEditor && _OpenInEditor(entry->ref, lineNum))
                                return;

                        // ask tracker to open it for us
                        BMessenger target(TRACKER_SIGNATURE);
                        BMessage message(B_REFS_RECEIVED);
                        message.AddRef("refs", &entry->ref);
                        if (lineNum > -1) {
                                message.AddInt32("be:line", lineNum);
                        }
                        target.SendMessage(&message);
                }
        }
}


void
GrepWindow::_OnSearchText()
{
        CALLED();

        bool enabled = fSearchText->TextView()->TextLength() != 0;
        fButton->SetEnabled(enabled);
        fSearch->SetEnabled(enabled);
        _StopNodeMonitoring();
}


void
GrepWindow::_OnHistoryItem(BMessage* message)
{
        const char* buf;
        if (message->FindString("text", &buf) == B_OK)
                fSearchText->SetText(buf);
}


void
GrepWindow::_OnTrimSelection()
{
        if (fSearchResults->CurrentSelection() < 0) {
                BString text;
                text << B_TRANSLATE("Please select the files you wish to keep searching.");
                text << "\n";
                text << B_TRANSLATE("The unselected files will be removed from the list.");
                text << "\n";
                BAlert* alert = new BAlert(NULL, text.String(), B_TRANSLATE("OK"), NULL, NULL,
                        B_WIDTH_AS_USUAL, B_WARNING_ALERT);
                alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
                alert->Go(NULL);
                return;
        }

        BMessage message;
        BString path;

        for (int32 index = 0; ; index++) {
                BStringItem* item = dynamic_cast<BStringItem*>(
                        fSearchResults->ItemAt(index));
                if (item == NULL)
                        break;

                // only open selected items
                if (!item->IsSelected())
                        continue;

                // get top level (file) item
                if (item->OutlineLevel() != 0)
                        item = dynamic_cast<BStringItem*>(fSearchResults->Superitem(item));
                if (item == NULL)
                        continue;

                if (path == item->Text())
                        continue;

                path = item->Text();
                entry_ref ref;
                if (get_ref_for_path(path.String(), &ref) == B_OK)
                        message.AddRef("refs", &ref);
        }

        fModel->fDirectory = entry_ref();
                // invalidated on purpose

        fModel->fSelectedFiles.MakeEmpty();
        fModel->fSelectedFiles = message;

        PostMessage(MSG_START_CANCEL);

        _SetWindowTitle();
}


void
GrepWindow::_OnCopyText()
{
        bool onlyCopySelection = true;

        if (fSearchResults->CurrentSelection() < 0)
                onlyCopySelection = false;

        BString buffer;

        for (int32 index = 0; ; index++) {
                BStringItem* item = dynamic_cast<BStringItem*>(
                        fSearchResults->ItemAt(index));
                if (item == NULL)
                        break;

                if (onlyCopySelection) {
                        if (item->IsSelected())
                                buffer << item->Text() << "\n";
                } else
                        buffer << item->Text() << "\n";
        }

        status_t status = B_OK;

        BMessage* clip = NULL;

        if (be_clipboard->Lock()) {
                be_clipboard->Clear();

                clip = be_clipboard->Data();

                clip->AddData("text/plain", B_MIME_TYPE, buffer.String(),
                        buffer.Length());

                status = be_clipboard->Commit();

                if (status != B_OK) {
                        be_clipboard->Unlock();
                        return;
                }

                be_clipboard->Unlock();
        }
}


void
GrepWindow::_OnSelectInTracker()
{
        if (fSearchResults->CurrentSelection() < 0) {
                BAlert* alert = new BAlert("Info",
                        B_TRANSLATE("Please select the files you wish to have selected for you in "
                                "Tracker."),
                        B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
                alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
                alert->Go(NULL);
                return;
        }

        BMessage message;
        BString filePath;
        BPath folderPath;
        BList folderList;
        BString lastFolderAddedToList;

        for (int32 index = 0; ; index++) {
                BStringItem* item = dynamic_cast<BStringItem*>(
                        fSearchResults->ItemAt(index));
                if (item == NULL)
                        break;

                // only open selected items
                if (!item->IsSelected())
                        continue;

                // get top level (file) item
                if (item->OutlineLevel() != 0)
                        item = dynamic_cast<BStringItem*>(fSearchResults->Superitem(item));
                if (item == NULL)
                        continue;

                // check if this was previously opened
                if (filePath == item->Text())
                        continue;

                filePath = item->Text();
                entry_ref file_ref;
                if (get_ref_for_path(filePath.String(), &file_ref) != B_OK)
                        continue;

                message.AddRef("refs", &file_ref);

                // add parent folder to list of folders to open
                folderPath.SetTo(filePath.String());
                if (folderPath.GetParent(&folderPath) == B_OK) {
                        BPath* path = new BPath(folderPath);
                        if (path->Path() != lastFolderAddedToList) {
                                // catches some duplicates
                                folderList.AddItem(path);
                                lastFolderAddedToList = path->Path();
                        } else
                                delete path;
                }
        }

        _RemoveFolderListDuplicates(&folderList);
        _OpenFoldersInTracker(&folderList);

        int32 aShortWhile = 100000;
        snooze(aShortWhile);

        if (!_AreAllFoldersOpenInTracker(&folderList)) {
                for (int32 x = 0; x < 5; x++) {
                        aShortWhile += 100000;
                        snooze(aShortWhile);
                        _OpenFoldersInTracker(&folderList);
                }
        }

        if (!_AreAllFoldersOpenInTracker(&folderList)) {
                BString str1;
                str1 << B_TRANSLATE("%APP_NAME couldn't open one or more folders.");
                str1.ReplaceFirst("%APP_NAME", B_TRANSLATE_NOCOLLECT(kAppName));
                BAlert* alert = new BAlert(NULL, str1.String(), B_TRANSLATE("OK"),
                        NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
                alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
                alert->Go(NULL);
                goto out;
        }

        _SelectFilesInTracker(&folderList, &message);

out:
        // delete folderList contents
        int32 folderCount = folderList.CountItems();
        for (int32 x = 0; x < folderCount; x++)
                delete static_cast<BPath*>(folderList.ItemAt(x));
}


void
GrepWindow::_OnQuitNow()
{
        if (be_app->Lock()) {
                be_app->PostMessage(B_QUIT_REQUESTED);
                be_app->Unlock();
        }
}


void
GrepWindow::_OnFileDrop(BMessage* message)
{
        if (fModel->fState != STATE_IDLE)
                return;

        entry_ref directory;
        _InitRefsReceived(&directory, message);

        fModel->fDirectory = directory;
        fModel->fSelectedFiles.MakeEmpty();
        fModel->fSelectedFiles = *message;

        fSearchResults->MakeEmpty();
        fOldPattern = "";

        _UpdateMenus();
        _SetWindowTitle();
}


void
GrepWindow::_OnRefsReceived(BMessage* message)
{
        _OnFileDrop(message);
        fOldPattern = "";
        // It seems a B_CANCEL always follows a B_REFS_RECEIVED
        // from a BFilePanel in Open mode.
        //
        // _OnOpenPanelCancel() is called on B_CANCEL.
        // That's where saving the current dir of the file panel occurs, for now,
        // and also the neccesary deletion of the file panel object.
        // A hidden file panel would otherwise jam the shutdown process.
}


void
GrepWindow::_OnOpenPanel()
{
        if (fFilePanel != NULL)
                return;

        entry_ref path;
        if (get_ref_for_path(fModel->fFilePanelPath.String(), &path) != B_OK) {
                printf("get_ref_for_path() failed for saved fFilePanelPath: '%s'\n",
                        fModel->fFilePanelPath.String());

                BPath home;
                if (find_directory(B_USER_DIRECTORY, &home) != B_OK)
                        home = "/boot/home";

                if (get_ref_for_path(home.Path(), &path) != B_OK) {
                        printf("'get_ref_for_path()' failed for: '%s'. Bailing out.\n", home.Path());
                        return;
                }
        }

        BMessenger messenger(this);
        BMessage message(MSG_REFS_RECEIVED);
        fFilePanel = new BFilePanel(B_OPEN_PANEL, &messenger, &path,
                B_FILE_NODE | B_DIRECTORY_NODE | B_SYMLINK_NODE, true,
                &message, NULL, true, true);

        fFilePanel->Show();
}


void
GrepWindow::_OnOpenPanelCancel()
{
        entry_ref panelDirRef;
        fFilePanel->GetPanelDirectory(&panelDirRef);
        BPath path(&panelDirRef);
        fModel->fFilePanelPath = path.Path();
        delete fFilePanel;
        fFilePanel = NULL;
}


void
GrepWindow::_OnSelectAll(BMessage* message)
{
        BMessenger messenger(fSearchResults);
        messenger.SendMessage(B_SELECT_ALL);
}


void
GrepWindow::_OnNewWindow()
{
        BMessage cloneRefs;
                // we don't want GrepWindow::InitRefsReceived()
                // to mess with the refs of the current window

        cloneRefs = fModel->fSelectedFiles;
        cloneRefs.AddRef("dir_ref", &(fModel->fDirectory));

        new GrepWindow(&cloneRefs);
}


void
GrepWindow::_OnSetTargetToParent()
{
        BEntry entry(&(fModel->fDirectory));
        BEntry parent;

        if (entry.GetParent(&parent) == B_OK) {
                entry_ref parent_ref;
                parent.GetRef(&parent_ref);

                BMessage parentRefs;
                parentRefs.AddRef("dir_ref", &parent_ref);
                _OnFileDrop(&parentRefs);
        }
}


void
GrepWindow::_OnGlobFilterChange()
{
        CALLED();
        fIncludeFilesGlob = fGlobText->Text();
        _StopNodeMonitoring();
}


// #pragma mark -


void
GrepWindow::_ModelChanged()
{
        CALLED();

        _StopNodeMonitoring();
        _SavePrefs();
}

bool
GrepWindow::_OpenInEditor(const entry_ref &ref, int32 lineNum)
{
        BMessage message(B_REFS_RECEIVED);
        message.AddRef("refs", &ref);

        if (lineNum != -1) {
                message.AddInt32("line", lineNum);      // for Pe
                message.AddInt32("be:line", lineNum);
        }

        // Find the preferred code editor
        char editorSig[B_MIME_TYPE_LENGTH];
        BMimeType mimeType("text/x-source-code");
        mimeType.GetPreferredApp(editorSig);

        entry_ref editor;
        if (be_roster->FindApp(editorSig, &editor) != B_OK)
                return false;

        if (be_roster->IsRunning(&editor)) {
                BMessenger msngr(NULL, be_roster->TeamFor(&editor));
                if (msngr.SendMessage(&message) != B_OK)
                        return false;
        } else {
                if (be_roster->Launch(&editor, &message) != B_OK)
                        return false;
        }

        return true;
}


void
GrepWindow::_RemoveFolderListDuplicates(BList* folderList)
{
        if (folderList == NULL)
                return;

        int32 folderCount = folderList->CountItems();
        BString folderX;
        BString folderY;

        for (int32 x = 0; x < folderCount; x++) {
                BPath* path = static_cast<BPath*>(folderList->ItemAt(x));
                folderX = path->Path();

                for (int32 y = x + 1; y < folderCount; y++) {
                        path = static_cast<BPath*>(folderList->ItemAt(y));
                        folderY = path->Path();
                        if (folderX == folderY) {
                                delete static_cast<BPath*>(folderList->RemoveItem(y));
                                folderCount--;
                                y--;
                        }
                }
        }
}


status_t
GrepWindow::_OpenFoldersInTracker(BList* folderList)
{
        status_t status = B_OK;
        BMessage refsMsg(B_REFS_RECEIVED);

        int32 folderCount = folderList->CountItems();
        for (int32 index = 0; index < folderCount; index++) {
                BPath* path = static_cast<BPath*>(folderList->ItemAt(index));

                entry_ref folderRef;
                status = get_ref_for_path(path->Path(), &folderRef);
                if (status != B_OK)
                        return status;

                status = refsMsg.AddRef("refs", &folderRef);
                if (status != B_OK)
                        return status;
        }

        status = be_roster->Launch(TRACKER_SIGNATURE, &refsMsg);
        if (status != B_OK && status != B_ALREADY_RUNNING)
                return status;

        return B_OK;
}


bool
GrepWindow::_AreAllFoldersOpenInTracker(BList* folderList)
{
        // Compare the folders we want open in Tracker to
        // the actual Tracker windows currently open.

        // We build a list of open Tracker windows, and compare
        // it to the list of folders we want open in Tracker.

        // If all folders exists in the list of Tracker windows
        // return true

        status_t status = B_OK;
        BMessenger trackerMessenger(TRACKER_SIGNATURE);
        BMessage sendMessage;
        BMessage replyMessage;
        BList windowList;

        if (!trackerMessenger.IsValid())
                return false;

        for (int32 count = 1; ; count++) {
                sendMessage.MakeEmpty();
                replyMessage.MakeEmpty();

                sendMessage.what = B_GET_PROPERTY;
                sendMessage.AddSpecifier("Path");
                sendMessage.AddSpecifier("Poses");
                sendMessage.AddSpecifier("Window", count);

                status = trackerMessenger.SendMessage(&sendMessage, &replyMessage);
                if (status != B_OK)
                        return false;

                entry_ref* trackerRef = new (nothrow) entry_ref;
                status = replyMessage.FindRef("result", trackerRef);
                if (status != B_OK || !windowList.AddItem(trackerRef)) {
                        delete trackerRef;
                        break;
                }
        }

        int32 folderCount = folderList->CountItems();
        int32 windowCount = windowList.CountItems();

        int32 found = 0;
        BPath* folderPath;
        entry_ref* windowRef;
        BString folderString;
        BString windowString;
        bool result = false;

        if (folderCount > windowCount) {
                // at least one folder is not open in Tracker
                goto out;
        }

        // Loop over the two lists and see if all folders exist as window
        for (int32 x = 0; x < folderCount; x++) {
                for (int32 y = 0; y < windowCount; y++) {

                        folderPath = static_cast<BPath*>(folderList->ItemAt(x));
                        windowRef = static_cast<entry_ref*>(windowList.ItemAt(y));

                        if (folderPath == NULL)
                                break;

                        if (windowRef == NULL)
                                break;

                        folderString = folderPath->Path();

                        BEntry entry;
                        BPath path;

                        if (entry.SetTo(windowRef) == B_OK && path.SetTo(&entry) == B_OK) {

                                windowString = path.Path();

                                if (folderString == windowString) {
                                        found++;
                                        break;
                                }
                        }
                }
        }

        result = found == folderCount;

out:
        // delete list of window entry_refs
        for (int32 x = 0; x < windowCount; x++)
                delete static_cast<entry_ref*>(windowList.ItemAt(x));

        return result;
}


status_t
GrepWindow::_SelectFilesInTracker(BList* folderList, BMessage* refsMessage)
{
        // loops over Tracker windows, find each windowRef,
        // extract the refs that are children of windowRef,
        // add refs to selection-message

        status_t status = B_OK;
        BMessenger trackerMessenger(TRACKER_SIGNATURE);
        BMessage windowSendMessage;
        BMessage windowReplyMessage;
        BMessage selectionSendMessage;
        BMessage selectionReplyMessage;

        if (!trackerMessenger.IsValid())
                return status;

        // loop over Tracker windows
        for (int32 windowCount = 1; ; windowCount++) {

                windowSendMessage.MakeEmpty();
                windowReplyMessage.MakeEmpty();

                windowSendMessage.what = B_GET_PROPERTY;
                windowSendMessage.AddSpecifier("Path");
                windowSendMessage.AddSpecifier("Poses");
                windowSendMessage.AddSpecifier("Window", windowCount);

                status = trackerMessenger.SendMessage(&windowSendMessage,
                        &windowReplyMessage);

                if (status != B_OK)
                        return status;

                entry_ref windowRef;
                status = windowReplyMessage.FindRef("result", &windowRef);
                if (status != B_OK)
                        break;

                int32 folderCount = folderList->CountItems();

                // loop over folders in folderList
                for (int32 x = 0; x < folderCount; x++) {
                        BPath* folderPath = static_cast<BPath*>(folderList->ItemAt(x));
                        if (folderPath == NULL)
                                break;

                        BString folderString = folderPath->Path();

                        BEntry windowEntry;
                        BPath windowPath;
                        BString windowString;

                        status = windowEntry.SetTo(&windowRef);
                        if (status != B_OK)
                                break;

                        status = windowPath.SetTo(&windowEntry);
                        if (status != B_OK)
                                break;

                        windowString = windowPath.Path();

                        // if match, loop over items in refsMessage
                        // and add those that live in window/folder
                        // to a selection message

                        if (windowString == folderString) {
                                selectionSendMessage.MakeEmpty();
                                selectionSendMessage.what = B_SET_PROPERTY;
                                selectionReplyMessage.MakeEmpty();

                                // loop over refs and add to message
                                entry_ref ref;
                                for (int32 index = 0; ; index++) {
                                        status = refsMessage->FindRef("refs", index, &ref);
                                        if (status != B_OK)
                                                break;

                                        BDirectory directory(&windowRef);
                                        BEntry entry(&ref);
                                        if (directory.Contains(&entry))
                                                selectionSendMessage.AddRef("data", &ref);
                                }

                                // finish selection message
                                selectionSendMessage.AddSpecifier("Selection");
                                selectionSendMessage.AddSpecifier("Poses");
                                selectionSendMessage.AddSpecifier("Window", windowCount);

                                trackerMessenger.SendMessage(&selectionSendMessage,
                                        &selectionReplyMessage);
                        }
                }
        }

        return B_OK;
}