jens-maus/yam

View on GitHub
src/mui/QuickSearchBar.c

Summary

Maintainability
Test Coverage
/***************************************************************************

 YAM - Yet Another Mailer
 Copyright (C) 1995-2000 Marcel Beck
 Copyright (C) 2000-2022 YAM Open Source Team

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.   See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

 YAM Official Support Site : http://www.yam.ch
 YAM OpenSource project     : http://sourceforge.net/projects/yamos/

 $Id$

 Superclass:  MUIC_Group
 Description: Provides functionality of a quick search bar in the main win

***************************************************************************/

#include "QuickSearchBar_cl.h"

#include <string.h>
#include <proto/muimaster.h>
#include <proto/timer.h>
#include <libraries/iffparse.h>
#include <mui/BetterString_mcc.h>
#include <mui/NList_mcc.h>
#include <mui/NListview_mcc.h>

#include "SDI_hook.h"

#include "YAM.h"
#include "YAM_find.h"
#include "YAM_mainFolder.h"
#include "YAM_utilities.h"

#include "BoyerMooreSearch.h"
#include "Busy.h"
#include "DynamicString.h"
#include "Locale.h"
#include "MailList.h"
#include "MUIObjects.h"

#include "mui/AddressBookWindow.h"
#include "mui/MainMailListGroup.h"

#include "Debug.h"

/* CLASSDATA
struct Data
{
  Object *CY_VIEWOPTIONS;
  Object *TX_STATUSTEXT;
  Object *ST_SEARCHSTRING;
  Object *BT_CLEAR;
  Object *GR_WHERE;
  Object *BT_FROM;
  Object *BT_TO;
  Object *BT_SUBJECT;
  Object *BT_BODY;
  struct TimeVal last_statusupdate;
  BOOL abortSearch;
  BOOL searchInProgress;
  char statusText[SIZE_DEFAULT];
};
*/

/* EXPORT
enum ViewOptions
{
  VO_ALL=0,
  VO_UNREAD,
  VO_NEW,
  VO_MARKED,
  VO_IMPORTANT,
  VO_LAST5DAYS,
  VO_KNOWNPEOPLE,
  VO_HASATTACHMENTS,
  VO_MINSIZE
};
*/

/* INCLUDE
#include "timeval.h"
*/

/* Enumerations */
enum SearchFlags
{
  SF_FROM=(1<<0),
  SF_TO=(1<<1),
  SF_SUBJECT=(1<<2),
  SF_BODY=(1<<3)
};

/* Private Functions */
/// MatchMail()
// function to actually check if a struct Mail* matches
// the currently active criteria
static BOOL MatchMail(const struct Mail *mail, enum ViewOptions vo,
                      ULONG searchFlags, const struct BoyerMooreContext *bmContext, struct TimeVal *curTimeUTC)
{
  BOOL foundMatch = FALSE;

  ENTER();

  // we first check for viewOption selection
  switch(vo)
  {
    // match all mails
    case VO_ALL:
      foundMatch = TRUE;
    break;

    // check for UNREAD mail status
    case VO_UNREAD:
      foundMatch = (!hasStatusRead(mail) || hasStatusNew(mail));
    break;

    // check for NEW mail status
    case VO_NEW:
      foundMatch = hasStatusNew(mail);
    break;

    // check for MARKED mail status
    case VO_MARKED:
      foundMatch = hasStatusMarked(mail);
    break;

    // check for the Important status
    case VO_IMPORTANT:
      foundMatch = getImportanceLevel(mail) == IMP_HIGH;
    break;

    // check if the mail is not older than 5 days (taken from the receive date)
    case VO_LAST5DAYS:
    {
      struct TimeVal now;

      memcpy(&now, curTimeUTC, sizeof(struct TimeVal));
      SubTime(TIMEVAL(&now), TIMEVAL(&mail->transDate));

      // check if after subtime now is <= 5 days
      foundMatch = (now.Seconds <= (5*24*60*60));
    }
    break;

    // check if the mail comes from a person we know
    case VO_KNOWNPEOPLE:
    {
      foundMatch = (FindPersonInABook(&G->abook, &mail->From) != NULL);
      if(foundMatch == FALSE && isMultiSenderMail(mail))
      {
        struct ExtendedMail *email;

        if((email = MA_ExamineMail(mail->Folder, mail->MailFile, TRUE)) != NULL)
        {
          int j;

          for(j=0; j < email->NumSFrom && foundMatch == FALSE; j++)
          {
            foundMatch = (FindPersonInABook(&G->abook, &email->SFrom[j]) != NULL);
          }

          MA_FreeEMailStruct(email);
        }
      }
    }
    break;

    // check if the mail has attachments
    case VO_HASATTACHMENTS:
    {
      foundMatch = isMP_MixedMail(mail);
    }
    break;

    // check if the mail has a size > 1MB
    case VO_MINSIZE:
    {
      foundMatch = (mail->Size > 1024*1024);
    }
    break;
  }

  // now we do a bit more complicated search if a search string
  // is specified as well
  if(foundMatch == TRUE && bmContext != NULL)
  {
    foundMatch = FALSE;

    // first check the simple things
    if(foundMatch == FALSE && isFlagSet(searchFlags, SF_FROM))
    {
      foundMatch = (BoyerMooreSearch(bmContext, mail->From.Address) != NULL ||
                    BoyerMooreSearch(bmContext, mail->From.RealName) != NULL);
    }
    if(foundMatch == FALSE && isFlagSet(searchFlags, SF_TO))
    {
      foundMatch = (BoyerMooreSearch(bmContext, mail->To.Address) != NULL ||
                    BoyerMooreSearch(bmContext, mail->To.RealName) != NULL);
    }
    if(foundMatch == FALSE && isFlagSet(searchFlags, SF_SUBJECT))
    {
      foundMatch = (BoyerMooreSearch(bmContext, mail->Subject) != NULL);
    }

    // now check the slightly more complex things
    if(foundMatch == FALSE)
    {
      if((isFlagSet(searchFlags, SF_FROM) && isMultiSenderMail(mail)) ||
         (isFlagSet(searchFlags, SF_TO) && isMultiRCPTMail(mail)))
      {
        struct ExtendedMail *email;

        if((email = MA_ExamineMail(mail->Folder, mail->MailFile, TRUE)) != NULL)
        {
          if(isFlagSet(searchFlags, SF_FROM))
          {
            int j;

            // search the additional From: addresses

            for(j=0; j < email->NumSFrom && foundMatch == FALSE; j++)
            {
              struct Person *pe = &email->SFrom[j];

              foundMatch = (BoyerMooreSearch(bmContext, pe->Address) != NULL ||
                            BoyerMooreSearch(bmContext, pe->RealName) != NULL);
            }
          }
          if(isFlagSet(searchFlags, SF_TO))
          {
            int j;

            // search the additional To: addresses
            for(j=0; j < email->NumSTo && foundMatch == FALSE; j++)
            {
              struct Person *to = &email->STo[j];

              foundMatch = (BoyerMooreSearch(bmContext, to->Address) != NULL ||
                            BoyerMooreSearch(bmContext, to->RealName) != NULL);
            }
            // search the CC: addresses
            for(j=0; j < email->NumCC && foundMatch == FALSE; j++)
            {
              struct Person *cc = &email->CC[j];

              foundMatch = (BoyerMooreSearch(bmContext, cc->Address) != NULL ||
                            BoyerMooreSearch(bmContext, cc->RealName) != NULL);
            }
          }

          MA_FreeEMailStruct(email);
        }
      }
    }

    // finally the most complex part, check the message contents
    if(foundMatch == FALSE && isFlagSet(searchFlags, SF_BODY))
    {
      struct ReadMailData *rmData;

      // allocate a private readmaildata object in which we readin
      // the mail text
      if((rmData = AllocPrivateRMData(mail, PM_TEXTS)) != NULL)
      {
        char *cmsg;

        if((cmsg = RE_ReadInMessage(rmData, RIM_QUIET)) != NULL)
        {
          // perform the search in the complete body
          foundMatch = (BoyerMooreSearch(bmContext, cmsg) != NULL);

          // free the allocated message text immediately
          dstrfree(cmsg);
        }

        FreePrivateRMData(rmData);
      }
    }
  }

  RETURN(foundMatch);
  return foundMatch;
}

///

/* Overloaded Methods */
/// OVERLOAD(OM_NEW)
OVERLOAD(OM_NEW)
{
  Object *CY_VIEWOPTIONS;
  Object *TX_STATUSTEXT;
  Object *ST_SEARCHSTRING;
  Object *BT_CLEAR;
  Object *GR_WHERE;
  Object *BT_FROM;
  Object *BT_TO;
  Object *BT_SUBJECT;
  Object *BT_BODY;
  static const char *viewOptions[10];

  viewOptions[0] = tr(MSG_QUICKSEARCH_VO_ALL);
  viewOptions[1] = tr(MSG_QUICKSEARCH_VO_UNREAD);
  viewOptions[2] = tr(MSG_QUICKSEARCH_VO_NEW);
  viewOptions[3] = tr(MSG_QUICKSEARCH_VO_MARKED);
  viewOptions[4] = tr(MSG_QUICKSEARCH_VO_IMPORTANT);
  viewOptions[5] = tr(MSG_QUICKSEARCH_VO_LAST5DAYS);
  viewOptions[6] = tr(MSG_QUICKSEARCH_VO_KNOWNPEOPLE);
  viewOptions[7] = tr(MSG_QUICKSEARCH_VO_HASATTACHMENTS);
  viewOptions[8] = tr(MSG_QUICKSEARCH_VO_MINSIZE);
  viewOptions[9] = NULL;

  if((obj = DoSuperNew(cl, obj,

    MUIA_Group_Horiz, FALSE,
    Child, HGroup,
      Child, HGroup,
        MUIA_Weight, 25,
        InnerSpacing(0,0),
        Child, MUI_MakeObject(MUIO_Label, (ULONG)tr(MSG_QUICKSEARCH_VIEW), MUIO_Label_Tiny),
        Child, CY_VIEWOPTIONS = CycleObject,
          MUIA_Font,          MUIV_Font_Tiny,
          MUIA_CycleChain,    TRUE,
          MUIA_Cycle_Entries, viewOptions,
          MUIA_ControlChar,   ShortCut(tr(MSG_QUICKSEARCH_VIEW)),
        End,
      End,

      Child, HGroup,
        MUIA_Weight, 50,
        Child, TX_STATUSTEXT = TextObject,
          MUIA_Font,          MUIV_Font_Tiny,
          MUIA_Text_PreParse, "\033c",
          MUIA_Text_Contents, " ",
          MUIA_Text_Copy,     FALSE,
        End,
      End,

      Child, HGroup,
        MUIA_Weight, 25,
        InnerSpacing(0,0),
        MUIA_Group_Spacing,    0,
        MUIA_Group_SameHeight, TRUE,

        Child, ST_SEARCHSTRING =  BetterStringObject,
          StringFrame,
          MUIA_ControlChar,                   tr(MSG_QUICKSEARCH_STR_CONTROLCHAR)[0],
          MUIA_CycleChain,                    TRUE,
          MUIA_Font,                          MUIV_Font_Tiny,
          MUIA_String_AdvanceOnCR,            FALSE,
          MUIA_BetterString_InactiveContents, tr(MSG_QUICKSEARCH_FILTER_LIST),
          MUIA_BetterString_NoShortcuts,      TRUE,
          MUIA_BetterString_SelectOnActive,   TRUE,
        End,
        Child, BT_CLEAR = MakeCloseButton(),
      End,
    End,
    Child, GR_WHERE = HGroup,
      MUIA_ShowMe, FALSE,
      Child, HSpace(0),
      Child, BT_FROM = TextObject,
        ButtonFrame,
        MUIA_CycleChain,    TRUE,
        MUIA_Font,          MUIV_Font_Tiny,
        MUIA_InputMode,     MUIV_InputMode_Toggle,
        MUIA_Background,    MUII_ButtonBack,
        MUIA_Selected,      TRUE,
        MUIA_Text_Contents, tr(MSG_QUICKSEARCH_FROM),
        MUIA_Text_SetMax,   TRUE,
        MUIA_Text_Copy,     FALSE,
      End,
      Child, BT_TO = TextObject,
        ButtonFrame,
        MUIA_CycleChain,    TRUE,
        MUIA_Font,          MUIV_Font_Tiny,
        MUIA_InputMode,     MUIV_InputMode_Toggle,
        MUIA_Background,    MUII_ButtonBack,
        MUIA_Selected,      TRUE,
        MUIA_Text_Contents, tr(MSG_QUICKSEARCH_TO),
        MUIA_Text_SetMax,   TRUE,
        MUIA_Text_Copy,     FALSE,
      End,
      Child, BT_SUBJECT = TextObject,
        ButtonFrame,
        MUIA_CycleChain,    TRUE,
        MUIA_Font,          MUIV_Font_Tiny,
        MUIA_InputMode,     MUIV_InputMode_Toggle,
        MUIA_Background,    MUII_ButtonBack,
        MUIA_Selected,      TRUE,
        MUIA_Text_Contents, tr(MSG_QUICKSEARCH_SUBJECT),
        MUIA_Text_SetMax,   TRUE,
        MUIA_Text_Copy,     FALSE,
      End,
      Child, BT_BODY = TextObject,
        ButtonFrame,
        MUIA_CycleChain,    TRUE,
        MUIA_Font,          MUIV_Font_Tiny,
        MUIA_InputMode,     MUIV_InputMode_Toggle,
        MUIA_Background,    MUII_ButtonBack,
        MUIA_Selected,      FALSE,
        MUIA_Text_Contents, tr(MSG_QUICKSEARCH_BODY),
        MUIA_Text_SetMax,   TRUE,
        MUIA_Text_Copy,     FALSE,
      End,
    End,

    TAG_MORE, inittags(msg))) != NULL)
  {
    GETDATA;

    data->CY_VIEWOPTIONS  = CY_VIEWOPTIONS;
    data->TX_STATUSTEXT   = TX_STATUSTEXT;
    data->ST_SEARCHSTRING = ST_SEARCHSTRING;
    data->BT_CLEAR        = BT_CLEAR;
    data->GR_WHERE        = GR_WHERE;
    data->BT_FROM         = BT_FROM;
    data->BT_TO           = BT_TO;
    data->BT_SUBJECT      = BT_SUBJECT;
    data->BT_BODY         = BT_BODY;

    // set the help text for each GUI element
    SetHelp(CY_VIEWOPTIONS,  MSG_HELP_QUICKSEARCH_VIEWOPTIONS);
    SetHelp(ST_SEARCHSTRING, MSG_HELP_QUICKSEARCH_SEARCHSTRING);

    // set notifies
    DoMethod(CY_VIEWOPTIONS,  MUIM_Notify, MUIA_Cycle_Active,       MUIV_EveryTime, obj,             2, METHOD(ViewOptionChanged), MUIV_TriggerValue);
    DoMethod(ST_SEARCHSTRING, MUIM_Notify, MUIA_String_Contents,    MUIV_EveryTime, obj,             3, METHOD(SearchContentChanged), MUIV_TriggerValue, FALSE);
    DoMethod(ST_SEARCHSTRING, MUIM_Notify, MUIA_String_Acknowledge, MUIV_EveryTime, obj,             3, METHOD(SearchContentChanged), MUIV_TriggerValue, TRUE);
    DoMethod(BT_CLEAR,        MUIM_Notify, MUIA_Pressed,            FALSE,          ST_SEARCHSTRING, 3, MUIM_Set, MUIA_String_Contents, "");
    DoMethod(BT_FROM,         MUIM_Notify, MUIA_Selected,           MUIV_EveryTime, obj,             1, METHOD(SearchFlagsChanged));
    DoMethod(BT_TO,           MUIM_Notify, MUIA_Selected,           MUIV_EveryTime, obj,             1, METHOD(SearchFlagsChanged));
    DoMethod(BT_SUBJECT,      MUIM_Notify, MUIA_Selected,           MUIV_EveryTime, obj,             1, METHOD(SearchFlagsChanged));
    DoMethod(BT_BODY,         MUIM_Notify, MUIA_Selected,           MUIV_EveryTime, obj,             1, METHOD(SearchFlagsChanged));
  }

  return (IPTR)obj;
}

///
/// OVERLOAD(OM_SET)
OVERLOAD(OM_SET)
{
  GETDATA;
  struct TagItem *tags = inittags(msg), *tag;

  while((tag = NextTagItem((APTR)&tags)) != NULL)
  {
    switch(tag->ti_Tag)
    {
      case ATTR(ViewOptions):
      {
        set(data->CY_VIEWOPTIONS, MUIA_Cycle_Active, tag->ti_Data);

        // make the superMethod call ignore those tags
        tag->ti_Tag = TAG_IGNORE;
      }
      break;

      case ATTR(AbortSearch):
      {
        data->abortSearch = tag->ti_Data;

        // make the superMethod call ignore those tags
        tag->ti_Tag = TAG_IGNORE;
      }
      break;

      // we only disable/enable what is really required to be disables/enabled
      case MUIA_Disabled:
      {
        set(data->CY_VIEWOPTIONS, MUIA_Disabled, tag->ti_Data);
        set(data->ST_SEARCHSTRING, MUIA_Disabled, tag->ti_Data);

        if(tag->ti_Data == TRUE)
          set(data->TX_STATUSTEXT, MUIA_Text_Contents, " ");

        // make the superMethod call ignore those tags
        tag->ti_Tag = TAG_IGNORE;
      }
      break;
    }
  }

  return DoSuperMethodA(cl, obj, msg);
}

///
/// OVERLOAD(OM_GET)
OVERLOAD(OM_GET)
{
  GETDATA;
  IPTR *store = ((struct opGet *)msg)->opg_Storage;

  switch(((struct opGet *)msg)->opg_AttrID)
  {
    case ATTR(ViewOptions):
    {
      *store = xget(data->CY_VIEWOPTIONS, MUIA_Cycle_Active);
      return TRUE;
    }
    break;

    case ATTR(SearchStringIsActive):
    {
      *store = (Object *)xget(_win(data->ST_SEARCHSTRING), MUIA_Window_ActiveObject) == data->ST_SEARCHSTRING;
      return TRUE;
    }
  }

  return DoSuperMethodA(cl, obj, msg);
}
///

/* Public Methods */
/// DECLARE(SearchContentChanged)
DECLARE(SearchContentChanged) // char *content, ULONG force
{
  GETDATA;

  ENTER();

  // abort any running search process
  set(obj, ATTR(AbortSearch), TRUE);

  // depending on if there is something to search for
  // we have to prepare something different
  if(IsStrEmpty(msg->content) == FALSE)
  {
    // we only start the actual search in case a minimum of two
    // characters are specified or the user pressed return explicitly
    if(msg->force == TRUE || msg->content[1] != '\0')
    {
      // make sure the clear button is shown and that
      // the correct mailview is displayed to the user
      DoMethod(obj, MUIM_MultiSet, MUIA_ShowMe, TRUE,
        data->BT_CLEAR,
        data->GR_WHERE,
        NULL);

      // now we issue a RestartTimer() command to schedule
      // the actual search in about 400ms from now on
      RestartTimer(TIMER_PROCESSQUICKSEARCH, 0, msg->force ? 1 : 500000, FALSE);
    }
  }
  else
  {
    // we check whether the view option is also set to "all"
    // and if so we clear the whole object
    if(xget(data->CY_VIEWOPTIONS, MUIA_Cycle_Active) == VO_ALL)
    {
      // first we make sure no waiting timer is scheduled
      StopTimer(TIMER_PROCESSQUICKSEARCH);

      // now we switch the ActivePage of the mailview pagegroup
      DoMethod(G->MA->GUI.PG_MAILLIST, MUIM_MainMailListGroup_SwitchToList, LT_MAIN);

      // now reset some other GUI elements as well.
      set(data->TX_STATUSTEXT, MUIA_Text_Contents, " ");
      DoMethod(obj, MUIM_MultiSet, MUIA_ShowMe, FALSE,
        data->BT_CLEAR,
        data->GR_WHERE,
        NULL);
    }
    else
    {
      // otherwise we issue a quicksearch start as well
      RestartTimer(TIMER_PROCESSQUICKSEARCH, 0, 500000, FALSE);
    }
  }

  RETURN(0);
  return 0;
}

///
/// DECLARE(ViewOptionChanged)
DECLARE(ViewOptionChanged) // int activeCycle
{
  GETDATA;
  char *searchContent = (char *)xget(data->ST_SEARCHSTRING, MUIA_String_Contents);

  ENTER();

  // abort any running search process
  set(obj, ATTR(AbortSearch), TRUE);

  // set the active group of the MAILVIEW pageGroup to 1 if one of the view
  // options is selected by the user
  G->quickSearchViewOptions = msg->activeCycle;
  if(msg->activeCycle == VO_ALL && (searchContent == NULL || searchContent[0] == '\0'))
  {
    DoMethod(obj, METHOD(Clear));
  }
  else
  {
    // immediately process the search, but make sure there is no
    // pending timerIO waiting already
    StopTimer(TIMER_PROCESSQUICKSEARCH);
    DoMethod(obj, METHOD(ProcessSearch));
  }

  RETURN(0);
  return 0;
}

///
/// DECLARE(SearchFlagsChanged)
DECLARE(SearchFlagsChanged)
{
  ENTER();

  // abort any running search process
  set(obj, ATTR(AbortSearch), TRUE);

  // immediately process the search, but make sure there is no
  // pending timerIO waiting already
  StopTimer(TIMER_PROCESSQUICKSEARCH);
  DoMethod(obj, METHOD(ProcessSearch));

  RETURN(0);
  return 0;
}

///
/// DECLARE(ProcessSearch)
DECLARE(ProcessSearch)
{
  GETDATA;
  struct Folder *curFolder;

  ENTER();

  curFolder = GetCurrentFolder();

  // a use of the quicksearchbar is only possible on
  // normal folders
  if(curFolder != NULL && !isGroupFolder(curFolder))
  {
    struct MailNode *mnode;
    enum ViewOptions viewOption = xget(data->CY_VIEWOPTIONS, MUIA_Cycle_Active);
    ULONG searchFlags;
    char *searchString = (char *)xget(data->ST_SEARCHSTRING, MUIA_String_Contents);
    struct TimeVal curTimeUTC;
    struct BoyerMooreContext *bmContext;
    struct BusyNode *busy;

    // get the current time in UTC
    GetSysTimeUTC(&curTimeUTC);

    // check the searchString settings for an empty string
    if(searchString != NULL && searchString[0] == '\0')
      searchString = NULL;

    // initialize a case insensitive Boyer/Moore search, searchString may be NULL
    bmContext = BoyerMooreInit(searchString, FALSE);

    searchFlags = 0;
    if(xget(data->BT_FROM, MUIA_Selected) == TRUE)
      setFlag(searchFlags, SF_FROM);
    if(xget(data->BT_TO, MUIA_Selected) == TRUE)
      setFlag(searchFlags, SF_TO);
    if(xget(data->BT_SUBJECT, MUIA_Selected) == TRUE)
      setFlag(searchFlags, SF_SUBJECT);
    if(xget(data->BT_BODY, MUIA_Selected) == TRUE)
      setFlag(searchFlags, SF_BODY);

    // make sure the correct mailview list is visible and quiet
    DoMethod(G->MA->GUI.PG_MAILLIST, MUIM_MainMailListGroup_SwitchToList, LT_QUICKVIEW);
    set(G->MA->GUI.PG_MAILLIST, MUIA_NList_Quiet, TRUE);

    // reset any previous abortion
    data->abortSearch = FALSE;
    data->searchInProgress = TRUE;

    // now we can process the search/sorting by searching the mail list of the
    // current folder querying different criterias of a mail
    LockMailListShared(curFolder->messages);

    busy = BusyBegin(BUSY_TEXT);
    BusyText(busy, tr(MSG_BUSY_SEARCHINGFOLDER), curFolder->Name);

    ForEachMailNode(curFolder->messages, mnode)
    {
      struct Mail *curMail = mnode->mail;

      // check if that mail matches the search/view criteria
      if(MatchMail(curMail, viewOption, searchFlags, bmContext, &curTimeUTC) == TRUE)
        DoMethod(G->MA->GUI.PG_MAILLIST, MUIM_MainMailListGroup_AddMailToList, LT_QUICKVIEW, curMail);

      DoMethod(_app(obj), MUIM_Application_InputBuffered);

      if(data->abortSearch == TRUE)
        break;
    }
    BusyEnd(busy);

    UnlockMailList(curFolder->messages);

    BoyerMooreCleanup(bmContext);

    // only update the GUI if this search was not aborted
    if(data->abortSearch == FALSE)
    {
      struct Mail *lastActiveMail;
      LONG pos = MUIV_NList_GetPos_Start;

      // make sure the statistics are updated as well
      DoMethod(obj, METHOD(UpdateStats), TRUE);

      // get the last active mail in the maillistgroup
      lastActiveMail = (struct Mail *)xget(G->MA->GUI.PG_MAILLIST, MUIA_MainMailListGroup_LastActiveMail);
      if(lastActiveMail != NULL)
      {
        // retrieve the number of the lastActive entry within the main mail listview
        DoMethod(G->MA->GUI.PG_MAILLIST, MUIM_NList_GetPos, lastActiveMail, &pos);
      }

      // make sure to set a new message so that the mail view is updated
      xset(G->MA->GUI.PG_MAILLIST, MUIA_NList_Active,       pos != MUIV_NList_GetPos_End && pos != MUIV_NList_GetPos_Start ? pos : MUIV_NList_Active_Top,
                                   MUIA_NList_SelectChange, TRUE);
    }

    // finally de-quiet the list again
    set(G->MA->GUI.PG_MAILLIST, MUIA_NList_Quiet, FALSE);
    data->searchInProgress = FALSE;
  }

  RETURN(0);
  return 0;
}

///
/// DECLARE(MatchMail)
// method to query the quick search bar to make a match if a certain mail matches
// the currently active criteria
DECLARE(MatchMail) // struct Mail *mail
{
  GETDATA;
  enum ViewOptions viewOption = xget(data->CY_VIEWOPTIONS, MUIA_Cycle_Active);
  ULONG searchFlags;
  char *searchString = (char *)xget(data->ST_SEARCHSTRING, MUIA_String_Contents);
  struct TimeVal curTimeUTC;
  struct BoyerMooreContext *bmContext;
  ULONG match;

  // get the current time in UTC
  GetSysTimeUTC(&curTimeUTC);

  // check the searchString settings for an empty string
  if(searchString != NULL && searchString[0] == '\0')
    searchString = NULL;

  // initialize a case insensitive Boyer/Moore search, searchString may be NULL
  bmContext = BoyerMooreInit(searchString, FALSE);

  searchFlags = 0;
  if(xget(data->BT_FROM, MUIA_Selected) == TRUE)
    setFlag(searchFlags, SF_FROM);
  if(xget(data->BT_TO, MUIA_Selected) == TRUE)
    setFlag(searchFlags, SF_TO);
  if(xget(data->BT_SUBJECT, MUIA_Selected) == TRUE)
    setFlag(searchFlags, SF_SUBJECT);
  if(xget(data->BT_BODY, MUIA_Selected) == TRUE)
    setFlag(searchFlags, SF_BODY);

  // now we check that a match is really required and if so we process it
  match = (ULONG)((viewOption != VO_ALL || searchString != NULL) &&
                  MatchMail(msg->mail, viewOption, searchFlags, bmContext, &curTimeUTC) == TRUE);

  BoyerMooreCleanup(bmContext);

  return match;
}

///
/// DECLARE(Clear)
// This method makes sure all quickbar entries are cleared and that the
// search NList is cleared as well
DECLARE(Clear)
{
  GETDATA;

  ENTER();

  // first we make sure no waiting timer is scheduled
  StopTimer(TIMER_PROCESSQUICKSEARCH);

  // now we switch the ActivePage of the mailview pagegroup
  DoMethod(G->MA->GUI.PG_MAILLIST, MUIM_MainMailListGroup_SwitchToList, LT_MAIN);

  // now we reset the quickbar's GUI elements
  nnset(data->ST_SEARCHSTRING, MUIA_String_Contents, "");
  nnset(data->CY_VIEWOPTIONS, MUIA_Cycle_Active, VO_ALL);
  set(data->TX_STATUSTEXT, MUIA_Text_Contents, " ");
  DoMethod(obj, MUIM_MultiSet, MUIA_ShowMe, FALSE,
    data->BT_CLEAR,
    data->GR_WHERE,
    NULL);

  // make sure our objects are not disabled
  set(obj, MUIA_Disabled, FALSE);

  RETURN(0);
  return 0;
}

///
/// DECALRE(UpdateStats)
DECLARE(UpdateStats) // ULONG force
{
  GETDATA;
  BOOL doUpdate = FALSE;

  ENTER();

  // now we check whether the user forces the update or if
  // we have to check that the display is not update too often
  if(msg->force == TRUE)
    doUpdate = TRUE;
  else
  {
    // then we update the gauge, but we take also care of not refreshing
    // it too often or otherwise it slows down the whole search process.
    doUpdate = TimeHasElapsed(&data->last_statusupdate, 250000);
  }

  if(doUpdate == TRUE)
  {
    ULONG numEntries = xget(G->MA->GUI.PG_MAILLIST, MUIA_NList_Entries);
    struct Folder *curFolder = GetCurrentFolder();

    snprintf(data->statusText, sizeof(data->statusText), tr(MSG_QUICKSEARCH_SHOWNMSGS), numEntries, curFolder->Total);

    // if the list is quiet then leave that state
    if(data->searchInProgress == TRUE)
      set(G->MA->GUI.PG_MAILLIST, MUIA_NList_Quiet, FALSE);
    set(data->TX_STATUSTEXT, MUIA_Text_Contents, data->statusText);
    // and restore the previous state if we changed it
    if(data->searchInProgress == TRUE)
      set(G->MA->GUI.PG_MAILLIST, MUIA_NList_Quiet, TRUE);
  }

  RETURN(0);
  return 0;
}

///
/// DECLARE(DoEditAction)
DECLARE(DoEditAction) // enum EditAction editAction
{
  GETDATA;
  Object *selectedObj = NULL;
  Object *windowObj = _win(obj);
  BOOL result = FALSE;

  ENTER();

  // we first check which object is current selected
  // as the 'active' Object
  if(windowObj != NULL)
  {
    selectedObj = (Object *)xget(windowObj, MUIA_Window_ActiveObject);
    if(selectedObj == NULL)
      selectedObj = (Object *)xget(windowObj, MUIA_Window_DefaultObject);
  }

  // if we still haven't got anything selected
  // something must be extremly strange ;)
  if(selectedObj != NULL)
  {
    // check which action we got
    switch(msg->editAction)
    {
      case EA_CUT:
      {
        if(selectedObj == data->ST_SEARCHSTRING)
        {
          DoMethod(selectedObj, MUIM_BetterString_DoAction, MUIV_BetterString_DoAction_Cut);
          result = TRUE;
        }
      }
      break;

      case EA_COPY:
      {
        if(selectedObj == data->ST_SEARCHSTRING)
        {
          DoMethod(selectedObj, MUIM_BetterString_DoAction, MUIV_BetterString_DoAction_Copy);
          result = TRUE;
        }
      }
      break;

      case EA_PASTE:
      {
        if(selectedObj == data->ST_SEARCHSTRING)
        {
          DoMethod(selectedObj, MUIM_BetterString_DoAction, MUIV_BetterString_DoAction_Paste);
          result = TRUE;
        }
      }
      break;

      case EA_DELETE:
      {
        if(selectedObj == data->ST_SEARCHSTRING)
        {
          DoMethod(selectedObj, MUIM_BetterString_DoAction, MUIV_BetterString_DoAction_Delete);
          result = TRUE;
        }
      }
      break;

      case EA_UNDO:
      {
        if(selectedObj == data->ST_SEARCHSTRING)
        {
          DoMethod(selectedObj, MUIM_BetterString_DoAction, MUIV_BetterString_DoAction_Undo);
          result = TRUE;
        }
      }
      break;

      case EA_REDO:
      {
        if(selectedObj == data->ST_SEARCHSTRING)
        {
          DoMethod(selectedObj, MUIM_BetterString_DoAction, MUIV_BetterString_DoAction_Redo);
          result = TRUE;
        }
      }
      break;

      case EA_SELECTALL:
      {
        if(selectedObj == data->ST_SEARCHSTRING)
        {
          DoMethod(selectedObj, MUIM_BetterString_DoAction, MUIV_BetterString_DoAction_SelectAll);
          result = TRUE;
        }
      }
      break;

      case EA_SELECTNONE:
      {
        if(selectedObj == data->ST_SEARCHSTRING)
        {
          DoMethod(selectedObj, MUIM_BetterString_DoAction, MUIV_BetterString_DoAction_SelectNone);
          result = TRUE;
        }
      }
      break;
    }
  }

  RETURN(result);
  return result;
}

///