jens-maus/yam

View on GitHub
src/mui/AddressBookListtree.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_NListtree
 Description: NListtree class of the addressbook

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

#include "AddressBookListtree_cl.h"

#include <ctype.h>
#include <string.h>

#include <proto/dos.h>
#include <proto/muimaster.h>
#include <mui/NList_mcc.h>
#include <mui/NListtree_mcc.h>

#include "YAM.h"

#include "mui/AddressBookEditWindow.h"
#include "mui/ImageArea.h"
#include "mui/MainMailListGroup.h"

#include "Config.h"
#include "Locale.h"
#include "MailList.h"
#include "MUIObjects.h"
#include "Requesters.h"

#include "Debug.h"

/* CLASSDATA
struct Data
{
  Object *listImage;
  ULONG sortBy;
  BOOL selfReorder;
  struct MUI_NListtree_TreeNode *activeTN;
  char dateStr[SIZE_SMALL];
  char aliasStr[SIZE_DEFAULT];
  struct MUI_EventHandlerNode eh;
};
*/

/* EXPORT
#define MUIV_AddressBookListtree_SortBy_Alias      0
#define MUIV_AddressBookListtree_SortBy_FirstName  1
#define MUIV_AddressBookListtree_SortBy_Coment     2
#define MUIV_AddressBookListtree_SortBy_Address    3
#define MUIV_AddressBookListtree_SortBy_Street     4
#define MUIV_AddressBookListtree_SortBy_City       5
#define MUIV_AddressBookListtree_SortBy_Country    6
#define MUIV_AddressBookListtree_SortBy_Phone      7
#define MUIV_AddressBookListtree_SortBy_Birthday   8
#define MUIV_AddressBookListtree_SortBy_PGPId      9
#define MUIV_AddressBookListtree_SortBy_LastName  10 // artificial column generated from the Name column

#define NUMBER_ABOOK_COLUMNS 9

#define MUIV_AddressBookListtree_Search_User          0
#define MUIV_AddressBookListtree_Search_RX            1
#define MUIV_AddressBookListtree_Search_RXName        2
#define MUIV_AddressBookListtree_Search_RXAddress     3
#define MUIV_AddressBookListtree_Search_RXNameAddress 4
*/

/* Private Functions */
/// BuildTreeEntry
struct BuildTreeStuff
{
  Object *obj;
  struct MUI_NListtree_TreeNode *parent[8];
  LONG nestLevel;
};

static BOOL BuildTreeEntry(const struct ABookNode *abn, ULONG flags, void *userData)
{
  struct BuildTreeStuff *stuff = (struct BuildTreeStuff *)userData;
  BOOL success = TRUE;

  ENTER();

  switch(abn->type)
  {
    case ABNT_USER:
    {
      if((APTR)DoMethod(stuff->obj, MUIM_NListtree_Insert, abn->Alias[0] != '\0' ? abn->Alias : abn->RealName, abn, stuff->parent[stuff->nestLevel], MUIV_NListtree_Insert_PrevNode_Tail, MUIF_NONE) == NULL)
        success = FALSE;
    }
    break;

    case ABNT_GROUP:
    {
      if(isFlagSet(flags, IABF_FIRST_GROUP_VISIT))
      {
        if((stuff->parent[stuff->nestLevel+1] = (struct MUI_NListtree_TreeNode *)DoMethod(stuff->obj, MUIM_NListtree_Insert, abn->Alias, abn, stuff->parent[stuff->nestLevel], MUIV_NListtree_Insert_PrevNode_Tail, TNF_LIST)) == NULL)
          success = FALSE;
        else
          stuff->nestLevel++;
      }
      else
      {
        stuff->nestLevel--;
      }
    }
    break;

    case ABNT_LIST:
    {
      if((APTR)DoMethod(stuff->obj, MUIM_NListtree_Insert, abn->Alias, abn, stuff->parent[stuff->nestLevel], MUIV_NListtree_Insert_PrevNode_Tail, MUIF_NONE) == NULL)
        success = FALSE;
    }
    break;
  }

  RETURN(success);
  return success;
}

///

/* Overloaded Methods */
/// OVERLOAD(OM_NEW)
OVERLOAD(OM_NEW)
{
  ENTER();

  if((obj = DoSuperNew(cl, obj,

    InputListFrame,
    MUIA_NListtree_DragDropSort,     TRUE,
    MUIA_NListtree_Title,            TRUE,
    MUIA_NListtree_EmptyNodes,       TRUE,
    MUIA_NListtree_MultiSelect,      MUIV_NListtree_MultiSelect_Default,
    MUIA_NList_AutoVisible,          TRUE,
    MUIA_NList_TitleClick,           TRUE,
    MUIA_NList_TitleClick2,          TRUE,
    MUIA_NList_TitleSeparator,       TRUE,
    MUIA_NList_DefaultObjectOnClick, FALSE,
    MUIA_Font,                       C->FixedFontList ? MUIV_NList_Font_Fixed : MUIV_NList_Font,

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

    // prepare the group image
    data->listImage = MakeImageObject("status_group", G->theme.statusImages[SI_GROUP]);
    DoMethod(obj, MUIM_NList_UseImage, data->listImage, 0, MUIF_NONE);

    DoMethod(obj, METHOD(MakeFormat));

    DoMethod(obj, MUIM_Notify, MUIA_NList_TitleClick, MUIV_EveryTime, MUIV_Notify_Self, 2, METHOD(SortBy), MUIV_TriggerValue);
    DoMethod(obj, MUIM_Notify, MUIA_NListtree_Active, MUIV_EveryTime, MUIV_Notify_Self, 3, MUIM_Set, ATTR(ActiveTreeNode), MUIV_TriggerValue);

    data->activeTN = (struct MUI_NListtree_TreeNode *)MUIV_NListtree_Active_Off;
    data->selfReorder = TRUE;
  }

  RETURN((IPTR)obj);
  return (IPTR)obj;
}

///
/// OVERLOAD(OM_DISPOSE)
OVERLOAD(OM_DISPOSE)
{
  GETDATA;

  // make sure that we free our group image
  DoMethod(obj, MUIM_NList_UseImage, NULL, 0, MUIF_NONE);
  MUI_DisposeObject(data->listImage);
  data->listImage = NULL;

  return DoSuperMethodA(cl, obj, msg);
}

///
/// OVERLOAD(OM_SET)
OVERLOAD(OM_SET)
{
  GETDATA;
  struct TagItem *tags, *tag;
  IPTR result;

  ENTER();

  tags = inittags(msg);
  while((tag = NextTagItem((APTR)&tags)) != NULL)
  {
    switch(tag->ti_Tag)
    {
      case ATTR(ActiveEntry):
      {
        struct ABookNode *abn = (struct ABookNode *)tag->ti_Data;

        data->activeTN = (struct MUI_NListtree_TreeNode *)DoMethod(obj, MUIM_NListtree_FindName, MUIV_NListtree_FindName_ListNode_Root, abn->Alias, MUIF_NONE);
        if(data->activeTN != NULL)
        {
          DoMethod(obj, MUIM_NListtree_Open, MUIV_NListtree_Open_ListNode_Parent, data->activeTN);
          nnset(obj, MUIA_NListtree_Active, data->activeTN);
        }
      }
      break;

      case ATTR(ActiveTreeNode):
      {
        data->activeTN = (struct MUI_NListtree_TreeNode *)tag->ti_Data;
      }
      break;
    }
  }

  result = DoSuperMethodA(cl, obj, msg);

  RETURN(result);
  return result;
}

///
/// OVERLOAD(OM_GET)
// get some stuff of our instance data
OVERLOAD(OM_GET)
{
  GETDATA;
  IPTR *store = ((struct opGet *)msg)->opg_Storage;
  IPTR result = FALSE;

  ENTER();

  switch(((struct opGet *)msg)->opg_AttrID)
  {
    case ATTR(DeleteEntryRequest): *store = TRUE; result = TRUE; break;
    case ATTR(ActiveGroup):
    {
      *store = (IPTR)NULL;
      if(data->activeTN != (struct MUI_NListtree_TreeNode *)MUIV_NListtree_ActiveList_Off)
      {
        struct MUI_NListtree_TreeNode *tn;

        tn = (struct MUI_NListtree_TreeNode *)DoMethod(obj, MUIM_NListtree_GetEntry, data->activeTN, MUIV_NListtree_GetEntry_Position_Parent, MUIF_NONE);
        if(tn != NULL)
          *store = (IPTR)tn->tn_User;
      }
    }
    break;
    case ATTR(ActiveEntry):        *store = (IPTR)(data->activeTN != NULL ? data->activeTN->tn_User : NULL); result = TRUE; break;
  }

  if(result == FALSE)
    result = DoSuperMethodA(cl, obj, msg);

  RETURN(result);
  return result;
}

///
/// OVERLOAD(MUIM_Setup)
OVERLOAD(MUIM_Setup)
{
  GETDATA;
  IPTR result;

  ENTER();

  if((result = DoSuperMethodA(cl, obj, msg)))
  {
    data->eh.ehn_Class  = cl;
    data->eh.ehn_Object = obj;
    data->eh.ehn_Events = IDCMP_RAWKEY;
    data->eh.ehn_Flags  = MUI_EHF_GUIMODE;
    data->eh.ehn_Priority = -1;

    if(_win(obj) != NULL)
      DoMethod(_win(obj), MUIM_Window_AddEventHandler, &data->eh);
  }

  RETURN(result);
  return result;
}

///
/// OVERLOAD(MUIM_Cleanup)
OVERLOAD(MUIM_Cleanup)
{
  GETDATA;
  IPTR result;

  ENTER();

  if(_win(obj) != NULL)
    DoMethod(_win(obj), MUIM_Window_RemEventHandler, &data->eh);

  result = DoSuperMethodA(cl, obj, msg);

  RETURN(result);
  return result;
}

///
/// OVERLOAD(MUIM_HandleEvent)
OVERLOAD(MUIM_HandleEvent)
{
  struct MUIP_HandleEvent *mhe = (struct MUIP_HandleEvent *)msg;
  IPTR result = 0;

  ENTER();

  if(mhe->imsg->Class == IDCMP_RAWKEY)
  {
    // check for DEL key without CAPS LOCK
    if(mhe->imsg->Code == 0x46 && isFlagClear(mhe->imsg->Qualifier, IEQUALIFIER_CAPSLOCK))
    {
      set(obj, ATTR(DeleteEntryRequest), TRUE);
      // eat the key press in any case
      result = MUI_EventHandlerRC_Eat;
    }
  }

  RETURN(result);
  return result;
}

///
/// OVERLOAD(MUIM_NListtree_Compare)
OVERLOAD(MUIM_NListtree_Compare)
{
  GETDATA;
  struct MUIP_NListtree_Compare *ncm = (struct MUIP_NListtree_Compare *)msg;
  struct ABookNode *ab1 = (struct ABookNode *)ncm->TreeNode1->tn_User;
  struct ABookNode *ab2 = (struct ABookNode *)ncm->TreeNode2->tn_User;
  LONG cmp = 0;

  ENTER();

  switch(data->sortBy)
  {
    case MUIV_AddressBookListtree_SortBy_Alias:
    {
      cmp = 0;
    }
    break;

    case MUIV_AddressBookListtree_SortBy_FirstName:
    {
      cmp = Stricmp(ab1->RealName, ab2->RealName);
    }
    break;

    case MUIV_AddressBookListtree_SortBy_Coment:
    {
      cmp = Stricmp(ab1->Comment, ab2->Comment);
    }
    break;

    case MUIV_AddressBookListtree_SortBy_Street:
    {
      cmp = Stricmp(ab1->Street, ab2->Street);
    }
    break;

    case MUIV_AddressBookListtree_SortBy_City:
    {
      cmp = Stricmp(ab1->City, ab2->City);
    }
    break;

    case MUIV_AddressBookListtree_SortBy_Country:
    {
      cmp = Stricmp(ab1->Country, ab2->Country);
    }
    break;

    case MUIV_AddressBookListtree_SortBy_Phone:
    {
      cmp = Stricmp(ab1->Phone, ab2->Phone);
    }
    break;

    case MUIV_AddressBookListtree_SortBy_Birthday:
    {
      cmp = ab1->Birthday - ab2->Birthday;
    }
    break;

    case MUIV_AddressBookListtree_SortBy_PGPId:
    {
      cmp = Stricmp(ab1->PGPId, ab2->PGPId);
    }
    break;

    case MUIV_AddressBookListtree_SortBy_LastName:
    {
      char *n1, *n2;

      if((n1 = strrchr(ab1->RealName,' ')) == NULL)
        n1 = ab1->RealName;
      if((n2 = strrchr(ab2->RealName,' ')) == NULL)
        n2 = ab2->RealName;
      cmp = Stricmp(n1, n2);
    }
    break;
  }

  if(cmp == 0)
    cmp = Stricmp(ab1->Alias, ab2->Alias);

  RETURN(cmp);
  return cmp;
}

///
/// OVERLOAD(MUIM_NListtree_Display)
OVERLOAD(MUIM_NListtree_Display)
{
  struct MUIP_NListtree_Display *ndm = (struct MUIP_NListtree_Display *)msg;

  ENTER();

  if(ndm->TreeNode != NULL)
  {
    GETDATA;
    struct ABookNode *entry = (struct ABookNode *)ndm->TreeNode->tn_User;

    BirthdayToString(entry->Birthday, data->dateStr, sizeof(data->dateStr));

    ndm->Array[0]  = entry->Alias;
    ndm->Array[1]  = entry->RealName;
    ndm->Array[2]  = entry->Comment;
    ndm->Array[3]  = entry->Address;
    ndm->Array[4]  = entry->Street;
    ndm->Array[5]  = entry->City;
    ndm->Array[6]  = entry->Country;
    ndm->Array[7]  = entry->Phone;
    ndm->Array[8]  = data->dateStr;
    ndm->Array[9]  = entry->PGPId;
    ndm->Array[10] = entry->Homepage;

    switch(entry->type)
    {
      case ABNT_GROUP:
      {
        ndm->Preparse[0] = (char *)MUIX_B;
        ndm->Preparse[2] = (char *)MUIX_B;
      }
      break;

      case ABNT_LIST:
      {
        snprintf(data->aliasStr, sizeof(data->aliasStr), "\033o[0]%s", entry->Alias);
        ndm->Array[0] = data->aliasStr;
      }
      break;

      default:
        // nothing
      break;
    }
  }
  else
  {
    ndm->Array[0]  = (STRPTR)tr(MSG_AB_TitleAlias);
    ndm->Array[1]  = (STRPTR)tr(MSG_AB_TitleName);
    ndm->Array[2]  = (STRPTR)tr(MSG_AB_TitleDescription);
    ndm->Array[3]  = (STRPTR)tr(MSG_AB_TitleAddress);
    ndm->Array[4]  = (STRPTR)tr(MSG_AB_TitleStreet);
    ndm->Array[5]  = (STRPTR)tr(MSG_AB_TitleCity);
    ndm->Array[6]  = (STRPTR)tr(MSG_AB_TitleCountry);
    ndm->Array[7]  = (STRPTR)tr(MSG_AB_TitlePhone);
    ndm->Array[8]  = (STRPTR)tr(MSG_AB_TitleBirthDate);
    ndm->Array[9]  = (STRPTR)tr(MSG_AB_TitlePGPId);
    ndm->Array[10] = (STRPTR)tr(MSG_AB_TitleHomepage);
  }

  RETURN(0);
  return 0;
}

///
/// OVERLOAD(MUIM_DragQuery)
OVERLOAD(MUIM_DragQuery)
{
  struct MUIP_DragQuery *d = (struct MUIP_DragQuery *)msg;
  ULONG result;

  ENTER();

  if(DoMethod(G->MA->GUI.PG_MAILLIST, MUIM_MainMailListGroup_IsMailList, d->obj) == TRUE)
    result = MUIV_DragQuery_Accept;
  else
    result = DoSuperMethodA(cl, obj, msg);

  RETURN(result);
  return result;
}

///
/// OVERLOAD(MUIM_DragDrop)
OVERLOAD(MUIM_DragDrop)
{
  struct MUIP_DragDrop *d = (struct MUIP_DragDrop *)msg;
  ULONG result = 0;

  ENTER();

  if(DoMethod(G->MA->GUI.PG_MAILLIST, MUIM_MainMailListGroup_IsMailList, d->obj) == TRUE)
  {
    struct MailList *mlist;

    if((mlist = MA_CreateMarkedList(d->obj, FALSE)) != NULL)
    {
      MA_GetAddress(mlist, (struct MUI_NListtree_TreeNode *)xget(obj, MUIA_NListtree_DropTarget),
                           xget(obj, MUIA_NListtree_DropType));

      DeleteMailList(mlist);
    }
  }
  else
    result = DoSuperMethodA(cl, obj, msg);

  RETURN(result);
  return result;
}

///
/// OVERLOAD(MUIM_NListtree_DropType)
OVERLOAD(MUIM_NListtree_DropType)
{
  struct MUIP_NListtree_DropType *dt = (struct MUIP_NListtree_DropType *)msg;
  struct MUI_NListtree_TreeNode *tn;

  ENTER();

  // get the current drop target
  if((tn = (struct MUI_NListtree_TreeNode *)xget(obj, MUIA_NListtree_DropTarget)) != NULL)
  {
    struct ABookNode *entry;

    if((entry = (struct ABookNode *)tn->tn_User) != NULL)
    {
      // If we drag an ABookNode on another ABookNode we abort the
      // DragReport immediately because we want to support drag operations
      // between ABookNode elements and groups is allowed
      if(*dt->Type == MUIV_NListtree_DropType_Onto && entry->type != ABNT_GROUP)
        *dt->Type = MUIV_NListtree_DropType_None;
    }
    else
      *dt->Type = MUIV_NListtree_DropType_None;
  }
  else
    *dt->Type = MUIV_NListtree_DropType_None;

  RETURN(0);
  return 0;
}

///
/// OVERLOAD(MUIM_NListtree_Insert)
OVERLOAD(MUIM_NListtree_Insert)
{
  GETDATA;
  struct MUIP_NListtree_Insert *mi = (struct MUIP_NListtree_Insert *)msg;
  struct MUI_NListtree_TreeNode *thisTN;

  ENTER();

  // first let the list tree class do the actual insertion of the tree nodes
  if((thisTN = (struct MUI_NListtree_TreeNode *)DoSuperMethodA(cl, obj, msg)) != NULL)
  {
    // reorder the address book only if we are explicitly told to do so
    if(data->selfReorder == TRUE)
    {
      struct ABookNode *groupABN;
      struct ABookNode *thisABN;
      struct MUI_NListtree_TreeNode *predTN;
      struct ABookNode *predABN;

      groupABN = (mi->ListNode != NULL && mi->ListNode->tn_User != NULL) ? (struct ABookNode *)mi->ListNode->tn_User : &G->abook.rootGroup;
      thisABN = (struct ABookNode *)thisTN->tn_User;

      // ideally we could use the mi->TreeNode pointer directly, but this might
      // be one of the special MUIV_#? values. Hence we better obtain the predecessor
      // node in the traditional way
      predTN = (struct MUI_NListtree_TreeNode *)DoMethod(obj, MUIM_NListtree_GetEntry, thisTN, MUIV_NListtree_GetEntry_Position_Previous, MUIF_NONE);
      predABN = (predTN != NULL) ? (struct ABookNode *)predTN->tn_User : NULL;

      // finally insert the node into the address book
      D(DBF_ABOOK, "insert entry '%s' behind entry '%s', group '%s'", thisABN->Alias, predABN != NULL ? predABN->Alias : "<head>", groupABN->Alias);
      AddABookNode(groupABN, thisABN, predABN);
      G->abook.modified = TRUE;
    }
  }

  RETURN(thisTN);
  return (IPTR)thisTN;
}

///
/// OVERLOAD(MUIM_NListtree_Move)
OVERLOAD(MUIM_NListtree_Move)
{
  IPTR result;
  struct MUIP_NListtree_Move *mv = (struct MUIP_NListtree_Move *)msg;
  struct ABookNode *groupABN;
  struct ABookNode *thisABN;
  struct MUI_NListtree_TreeNode *predTN;
  struct ABookNode *predABN;

  ENTER();

  // first let the list tree class do the actual movement of the tree nodes
  result = DoSuperMethodA(cl, obj, msg);

  groupABN = (mv->NewListNode->tn_User != NULL) ? (struct ABookNode *)mv->NewListNode->tn_User : &G->abook.rootGroup;
  thisABN = (struct ABookNode *)mv->OldTreeNode->tn_User;

  // ideally we could use the mv->NewTreeNode pointer directly, but this might
  // be one of the special MUIV_#? values. Hence we better obtain the predecessor
  // node in the traditional way
  predTN = (struct MUI_NListtree_TreeNode *)DoMethod(obj, MUIM_NListtree_GetEntry, mv->OldTreeNode, MUIV_NListtree_GetEntry_Position_Previous, MUIF_NONE);
  predABN = (predTN != NULL) ? (struct ABookNode *)predTN->tn_User : NULL;

  // finally move the node within the address book
  D(DBF_ABOOK, "move entry '%s' behind entry '%s', group '%s'", thisABN->Alias, predABN != NULL ? predABN->Alias : "<head>", groupABN->Alias);
  MoveABookNode(groupABN, thisABN, predABN);
  G->abook.modified = TRUE;

  RETURN(result);
  return result;
}

///

/* Public Methods */
/// DECLARE(BuildTree)
// (re)build the NListtree from the global address book
DECLARE(BuildTree)
{
  GETDATA;
  struct BuildTreeStuff stuff;

  ENTER();

  data->selfReorder = FALSE;

  set(obj, MUIA_NListtree_Quiet, TRUE);
  DoMethod(obj, MUIM_NListtree_Clear);
  stuff.obj = obj;
  stuff.parent[0] = MUIV_NListtree_Insert_ListNode_Root;
  stuff.nestLevel = 0;
  IterateABook(&G->abook, IABF_VISIT_GROUPS_TWICE, BuildTreeEntry, &stuff);
  set(obj, MUIA_NListtree_Quiet, FALSE);

  data->selfReorder = TRUE;

  RETURN(0);
  return 0;
}

///
/// DECLARE(BuildABook)
// (re)build the global address book from the NListtree
DECLARE(BuildABook)
{
  struct ABook tempABook;
  struct MUI_NListtree_TreeNode *tn;
  struct MUI_NListtree_TreeNode *parentTN[8];
  struct ABookNode *groupABN[8];
  struct ABookNode *afterThisABN[8];
  ULONG nestLevel;
  ULONG i;

  ENTER();

  // we need a temporary address book, because me move all entries
  // from the global book to the temporary one
  InitABook(&tempABook, NULL);

  parentTN[0] = MUIV_NListtree_GetEntry_ListNode_Root;
  groupABN[0] = &tempABook.rootGroup;
  afterThisABN[0] = NULL;
  nestLevel = 0;
  i = 0;
  while((tn = (struct MUI_NListtree_TreeNode *)DoMethod(obj, MUIM_NListtree_GetEntry, MUIV_NListtree_GetEntry_ListNode_Root, i, MUIF_NONE)) != NULL)
  {
    struct ABookNode *abn = (struct ABookNode *)tn->tn_User;

    if(i == 0)
    {
      // if this is the first iteration we must obtain the parent tree node
      // of the current one to make sure we have a valid pointer to compare
      // instead of the artificial internal number.
      parentTN[0] = (struct MUI_NListtree_TreeNode *)DoMethod(obj, MUIM_NListtree_GetEntry, tn, MUIV_NListtree_GetEntry_Position_Parent, MUIF_NONE);
    }

    if(abn->type == ABNT_GROUP)
    {
      // move group nodes before bumping the nesting level
      MoveABookNode(groupABN[nestLevel], abn, afterThisABN[nestLevel]);
      afterThisABN[nestLevel] = abn;

      // bump the nesting level and remember the new group details
      nestLevel++;
      parentTN[nestLevel] = tn;
      groupABN[nestLevel] = abn;
      afterThisABN[nestLevel] = NULL;
    }
    else
    {
      // check if our parent treenode changed
      struct MUI_NListtree_TreeNode *parent;

      parent = (struct MUI_NListtree_TreeNode *)DoMethod(obj, MUIM_NListtree_GetEntry, tn, MUIV_NListtree_GetEntry_Position_Parent, MUIF_NONE);
      if(parent != parentTN[nestLevel] && nestLevel != 0)
      {
        // the parent treenode has changed, so go back until we find the correct treenode
        do
        {
          nestLevel--;
        }
        while(nestLevel != 0 && parent != parentTN[nestLevel]);

        // replace the artificial MUIV_#? root pointer by the true root pointer
        if(nestLevel == 0 && parentTN[0] == NULL)
          parentTN[0] = parent;
      }

      // move user and list nodes after bumping the nesting level
      MoveABookNode(groupABN[nestLevel], abn, afterThisABN[nestLevel]);
      afterThisABN[nestLevel] = abn;
    }

    i++;
  }

  // finally move all nodes back to the global address book and mark it as modified
  MoveABookNodes(&G->abook, &tempABook);
  G->abook.modified = TRUE;

  RETURN(0);
  return 0;
}

///
/// DECLARE(SortBy)
DECLARE(SortBy) // ULONG sortBy
{
  GETDATA;

  ENTER();

  data->sortBy = msg->sortBy;
  DoMethod(obj, MUIM_NListtree_Sort, MUIV_NListtree_Sort_ListNode_Root, MUIV_NListtree_Sort_Flag_RecursiveAll);
  // rebuild the address book from the sorted listtree
  DoMethod(obj, METHOD(BuildABook));

  RETURN(0);
  return 0;
}

///
/// DECLARE(MakeFormat)
//  Creates format definition for address book listtree
DECLARE(MakeFormat)
{
  int i;
  char format[SIZE_LARGE];
  BOOL first = TRUE;

  ENTER();

  format[0] = '\0';

  // start at zero here, otherwise the tree column will be missing
  for(i = 0; i < NUMBER_ABOOK_COLUMNS; i++)
  {
    if(isFlagSet(C->AddrbookCols, (1<<i)))
    {
      int p;

      if(first == TRUE)
        first = FALSE;
      else
        strlcat(format, " BAR,", sizeof(format));

      p = strlen(format);

      snprintf(&format[p], sizeof(format)-p, "COL=%d W=-1", i);
    }
  }

  set(obj, MUIA_NListtree_Format, format);

  RETURN(0);
  return 0;
}

///
/// DECLARE(FoldTree)
// (un)fold the complete tree
DECLARE(FoldTree) // ULONG unfold
{
  ENTER();

  if(msg->unfold == TRUE)
    DoMethod(obj, MUIM_NListtree_Open, MUIV_NListtree_Open_ListNode_Root, MUIV_NListtree_Open_TreeNode_All, MUIF_NONE);
  else
    DoMethod(obj, MUIM_NListtree_Close, MUIV_NListtree_Close_ListNode_Root, MUIV_NListtree_Close_TreeNode_All, MUIF_NONE);

  RETURN(0);
  return 0;
}

///
/// DECLARE(IncrementalSearch)
// incrementally searches the address book node for a given pattern
DECLARE(IncrementalSearch) // const char *pattern, ULONG *iterator
{
  struct ABookNode *foundABN = NULL;
  struct MUI_NListtree_TreeNode *tn;
  ULONG i;

  ENTER();

  D(DBF_ABOOK, "searching for pattern '%s'", msg->pattern);

  i = *msg->iterator;

  do
  {
    if((tn = (struct MUI_NListtree_TreeNode *)DoMethod(obj, MUIM_NListtree_GetEntry, MUIV_NListtree_GetEntry_ListNode_Root, i, MUIF_NONE)) != NULL)
    {
      struct ABookNode *abn = (struct ABookNode *)tn->tn_User;

      if(abn->type == ABNT_USER || abn->type == ABNT_LIST)
      {
        BOOL found;

        D(DBF_ABOOK, "match entry with alias '%s'", abn->Alias);

        found = MatchNoCase(abn->Alias, msg->pattern) ||
                MatchNoCase(abn->Comment, msg->pattern);
        if(found == FALSE && abn->type == ABNT_USER)
        {
          found = MatchNoCase(abn->RealName, msg->pattern) ||
                  MatchNoCase(abn->Address, msg->pattern)  ||
                  MatchNoCase(abn->Homepage, msg->pattern) ||
                  MatchNoCase(abn->Street, msg->pattern)   ||
                  MatchNoCase(abn->City, msg->pattern)     ||
                  MatchNoCase(abn->Country, msg->pattern)  ||
                  MatchNoCase(abn->Phone, msg->pattern);
        }

        if(found == TRUE)
        {
          D(DBF_ABOOK, "found pattern '%s' in entry with address '%s'", msg->pattern, abn->Address);

          DoMethod(obj, MUIM_NListtree_Open, MUIV_NListtree_Open_ListNode_Parent, tn, MUIF_NONE);
          set(obj, MUIA_NListtree_Active, tn);

          foundABN = abn;
          // return the next to be examined index
          *msg->iterator = i+1;

          break;
        }
      }
    }

    i++;
  }
  while(tn != NULL);

  RETURN(foundABN);
  return (IPTR)foundABN;
}

///