jens-maus/yam

View on GitHub
src/mui/MainFolderListtree.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_FolderListtree
 Description: NListtree class of the main folder list in the main window

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

#include "MainFolderListtree_cl.h"

#include <string.h>
#include <proto/dos.h>
#include <proto/muimaster.h>
#include <libraries/gadtools.h>
#include <libraries/iffparse.h>
#include <mui/NList_mcc.h>
#include <mui/NListtree_mcc.h>

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

#include "mui/FolderEditWindow.h"
#include "mui/ImageArea.h"
#include "mui/MainMailListGroup.h"
#include "mui/YAMApplication.h"

#include "Config.h"
#include "FolderList.h"
#include "Locale.h"
#include "MUIObjects.h"
#include "Requesters.h"
#include "Themes.h"

#include "Debug.h"

/* CLASSDATA
struct Data
{
  Object *contextMenu;
  Object *folderEditWindow;
  struct Folder newFolder;
  BOOL draggingMails;
  BOOL reorderFolderList;
  struct MUI_EventHandlerNode eh;
};
*/

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

/* EXPORT
#define NUMBER_FOLDERTREE_COLUMNS 5
*/

enum
{
  CMN_EDITF=10,
  CMN_DELETEF,
  CMN_INDEX,
  CMN_NEWF,
  CMN_NEWFG,
  CMN_EMPTYTRASH,
  CMN_EMPTYSPAM,
  CMN_ALLTOREAD,
  CMN_SEARCH
};

/* Private Functions */

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

  if((obj = DoSuperNew(cl, obj,

    InputListFrame,
    MUIA_ObjectID,                    MAKE_ID('N','L','0','1'),
    MUIA_ContextMenu,                 C->FolderCntMenu ? MUIV_NList_ContextMenu_Always : 0,
    MUIA_Font,                        C->FixedFontList ? MUIV_NList_Font_Fixed : MUIV_NList_Font,
    MUIA_Dropable,                    TRUE,
    MUIA_NList_DragType,              MUIV_NList_DragType_Immediate,
    MUIA_NList_DragSortable,          TRUE,
    MUIA_NList_ActiveObjectOnClick,   TRUE,
    MUIA_NList_DefaultObjectOnClick,  FALSE,
    MUIA_NList_Exports,               MUIV_NList_Exports_ColWidth|MUIV_NList_Exports_ColOrder,
    MUIA_NList_Imports,               MUIV_NList_Imports_ColWidth|MUIV_NList_Imports_ColOrder,
    MUIA_NListtree_DragDropSort,      TRUE,
    MUIA_NListtree_Title,             TRUE,
    MUIA_NListtree_DoubleClick,       MUIV_NListtree_DoubleClick_All,

    TAG_MORE, inittags(msg))) != NULL)
  {
    DoMethod(obj, MUIM_Notify, MUIA_NList_DoubleClick, MUIV_EveryTime, MUIV_Notify_Self, 2, METHOD(EditFolder), TRUE);
    //DoMethod(obj, MUIM_Notify, MUIA_NList_TitleClick,    MUIV_EveryTime, MUIV_Notify_Self, 3, MUIM_NList_Sort2,          MUIV_TriggerValue,MUIV_NList_SortTypeAdd_2Values);
    //DoMethod(obj, MUIM_Notify, MUIA_NList_SortType,      MUIV_EveryTime, MUIV_Notify_Self, 3, MUIM_Set,                  MUIA_NList_TitleMark,MUIV_TriggerValue);
    DoMethod(obj, MUIM_Notify, MUIA_NListtree_Active, MUIV_EveryTime, MUIV_Notify_Self, 2, METHOD(ChangeFolder), MUIV_TriggerValue);

    DoMethod(obj, METHOD(MakeFormat));
  }

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

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

  ENTER();

  // make sure that our context menus are also disposed
  if(data->contextMenu != NULL)
    MUI_DisposeObject(data->contextMenu);

  // dispose ourself
  result = DoSuperMethodA(cl, obj, msg);

  RETURN(result);
  return result;
}

///
/// 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(ReorderFolderList):
      {
        data->reorderFolderList = tag->ti_Data;
        // make the superMethod call ignore those tags
        tag->ti_Tag = TAG_IGNORE;
      }
      break;
    }
  }

  return DoSuperMethodA(cl, obj, msg);
}

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

  switch(((struct opGet *)msg)->opg_AttrID)
  {
    case ATTR(TreeChanged): *store = TRUE; return TRUE;
  }

  return DoSuperMethodA(cl, obj, msg);
}

///
/// 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)
  {
    if(mhe->imsg->Code >= 1 && mhe->imsg->Code <= 10)
    {
      struct MUI_NListtree_TreeNode *tn = NULL;
      int count = mhe->imsg->Code;
      int i;

      // we get the first entry and if it's a LIST we have to get the next one
      // and so on, until we have a real entry for that key or we set nothing active
      for(i=count; i <= count; i++)
      {
        tn = (struct MUI_NListtree_TreeNode *)DoMethod(obj, MUIM_NListtree_GetEntry, MUIV_NListtree_GetEntry_ListNode_Root, i-1, MUIF_NONE);
        if(tn == NULL)
          break;

        if(isFlagSet(tn->tn_Flags, TNF_LIST))
          count++;
      }

      if(tn != NULL)
      {
        // force that the list is open at this entry
        DoMethod(obj, MUIM_NListtree_Open, MUIV_NListtree_Open_ListNode_Parent, tn, MUIF_NONE);
        // now set this treenode active
        set(obj, MUIA_NListtree_Active, tn);
      }

      // eat the key press in any case
      result = MUI_EventHandlerRC_Eat;
    }
  }

  RETURN(result);
  return result;
}

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

  ENTER();

  // check if the object that requests the drag operation
  // is a mail list object or not
  if(DoMethod(G->MA->GUI.PG_MAILLIST, MUIM_MainMailListGroup_IsMailList, dq->obj) == TRUE)
  {
    data->draggingMails = TRUE;
    result = MUIV_DragQuery_Accept;
  }
  else
  {
    data->draggingMails = FALSE;
    result = DoSuperMethodA(cl, obj, msg);
  }

  RETURN(result);
  return result;
}

///
/// OVERLOAD(MUIM_DragDrop)
OVERLOAD(MUIM_DragDrop)
{
  IPTR result;
  struct MUIP_DragDrop *dd = (struct MUIP_DragDrop *)msg;

  ENTER();

  if(dd->obj == obj)
  {
    // A folder was dragged onto another folder we break here and
    // let the super class do the dirty work. This will invoke the
    // MUIM_NListtree_Move method, which we also catch to move the
    // folder node within our folder list.
    result = DoSuperMethodA(cl, obj, msg);
  }
  else
  {
    struct MUI_NListtree_TreeNode *tn_dst;

    if((tn_dst = (struct MUI_NListtree_TreeNode *)xget(obj, MUIA_NListtree_DropTarget)) != NULL)
    {
      struct Folder *dstfolder = ((struct FolderNode *)tn_dst->tn_User)->folder;

      if(isGroupFolder(dstfolder) == FALSE)
        MA_MoveCopy(NULL, dstfolder, "manual drag", MVCPF_CLOSE_WINDOWS);
    }

    result = 0;
  }

  RETURN(result);
  return result;
}

///
/// OVERLOAD(MUIM_NListtree_DropType)
OVERLOAD(MUIM_NListtree_DropType)
{
  GETDATA;
  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 Folder *folder;

    if((folder = ((struct FolderNode *)tn->tn_User)->folder) != NULL)
    {
      if(data->draggingMails == TRUE)
      {
        // if mails are being dragged the currently active folder and group folders must be excluded.
        // All other folders are valid drop targets.
        if(*dt->Pos == (LONG)xget(obj, MUIA_NListtree_Active) || isGroupFolder(folder))
        {
          if(isFlagSet(tn->tn_Flags, TNF_LIST) && isFlagClear(tn->tn_Flags, TNF_OPEN))
            *dt->Type = MUIV_NListtree_DropType_Onto;
          else
            *dt->Type = MUIV_NListtree_DropType_None;
        }
        else
          *dt->Type = MUIV_NListtree_DropType_Onto;
      }
      else
      {
        // if folders are being dragged only group folders are valid drop targets. Else we place the
        // folder being dragged above the current folder below the mouse pointer.
        if(*dt->Type == MUIV_NListtree_DropType_Onto && !isGroupFolder(folder))
          *dt->Type = MUIV_NListtree_DropType_Above;
      }
    }
    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 MUI_NListtree_TreeNode *thisTreeNode;

  ENTER();

  // first let the list tree class do the actual insertion of the tree nodes
  if((thisTreeNode = (struct MUI_NListtree_TreeNode *)DoSuperMethodA(cl, obj, msg)) != NULL)
  {
    // reorder the folder list only if we are explicitly told to do so
    if(data->reorderFolderList == TRUE)
    {
      struct MUI_NListtree_TreeNode *prevTreeNode;

      // now determine the previous node
      if((prevTreeNode = (struct MUI_NListtree_TreeNode *)DoMethod(obj, MUIM_NListtree_GetEntry, thisTreeNode, MUIV_NListtree_GetEntry_Position_Previous, MUIF_NONE)) != NULL)
      {
        struct FolderNode *thisFNode;
        struct FolderNode *prevFNode;
        struct MUI_NListtree_TreeNode *parentTreeNode;

        thisFNode = (struct FolderNode *)thisTreeNode->tn_User;
        prevFNode = (struct FolderNode *)prevTreeNode->tn_User;

        // if the folder is to be moved behind a group folder then we have
        // to get the bottom-most leaf of that group, otherwise the global
        // folder list will be screwed up. This must be iterated through all
        // nested groups.
        while(isGroupFolder(prevFNode->folder))
        {
          prevTreeNode = (struct MUI_NListtree_TreeNode *)DoMethod(obj, MUIM_NListtree_GetEntry, prevTreeNode, MUIV_NListtree_GetEntry_Position_Tail, MUIF_NONE);
          prevFNode = (struct FolderNode *)prevTreeNode->tn_User;
        }

        // set the parent treenode of the new folder
        parentTreeNode = (struct MUI_NListtree_TreeNode *)DoMethod(obj, MUIM_NListtree_GetEntry, thisTreeNode, MUIV_NListtree_GetEntry_Position_Parent, MUIF_NONE);
        thisFNode->folder->parent = (struct FolderNode *)parentTreeNode->tn_User;

        // finally move the folder node within the exclusively locked folder list
        D(DBF_FOLDER, "insert folder '%s' behind folder '%s'", thisFNode->folder->Name, prevFNode->folder->Name);
        LockFolderList(G->folders);
        MoveFolderNode(G->folders, thisFNode, prevFNode);
        UnlockFolderList(G->folders);
      }
    }

    // trigger a changed folder tree
    set(obj, ATTR(TreeChanged), TRUE);
  }

  RETURN(thisTreeNode);
  return (IPTR)thisTreeNode;
}

///
/// OVERLOAD(MUIM_NListtree_Move)
OVERLOAD(MUIM_NListtree_Move)
{
  IPTR result;
  struct MUIP_NListtree_Move *mv = (struct MUIP_NListtree_Move *)msg;
  struct MUI_NListtree_TreeNode *prevTreeNode;

  ENTER();

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

  // now determine the previous node
  if((prevTreeNode = (struct MUI_NListtree_TreeNode *)DoMethod(obj, MUIM_NListtree_GetEntry, mv->OldTreeNode, MUIV_NListtree_GetEntry_Position_Previous, MUIF_NONE)) != NULL)
  {
    struct FolderNode *thisFNode;
    struct FolderNode *prevFNode;

    thisFNode = (struct FolderNode *)mv->OldTreeNode->tn_User;
    prevFNode = (struct FolderNode *)prevTreeNode->tn_User;

    // if the folder is to be moved behind a group folder then we have
    // to get the bottom-most leaf of that group, otherwise the global
    // folder list will be screwed up. This must be iterated through all
    // nested groups.
    while(isGroupFolder(prevFNode->folder))
    {
      prevTreeNode = (struct MUI_NListtree_TreeNode *)DoMethod(obj, MUIM_NListtree_GetEntry, prevTreeNode, MUIV_NListtree_GetEntry_Position_Tail, MUIF_NONE);
      prevFNode = (struct FolderNode *)prevTreeNode->tn_User;
    }

    // finally move the folder node within the exclusively locked folder list
    D(DBF_FOLDER, "move folder '%s' behind folder '%s'", thisFNode->folder->Name, prevFNode->folder->Name);
    LockFolderList(G->folders);
    MoveFolderNode(G->folders, thisFNode, prevFNode);
    UnlockFolderList(G->folders);

    // trigger a changed folder tree
    set(obj, ATTR(TreeChanged), TRUE);
  }

  RETURN(result);
  return result;
}

///
/// OVERLOAD(MUIM_NList_ContextMenuBuild)
OVERLOAD(MUIM_NList_ContextMenuBuild)
{
  GETDATA;
  struct MUIP_NList_ContextMenuBuild *m = (struct MUIP_NList_ContextMenuBuild *)msg;
  struct MUI_NListtree_TestPos_Result r;
  struct MUI_NListtree_TreeNode *tn;
  struct Folder *folder = NULL;
  Object *lastItem;
  BOOL disable_delete = FALSE;
  BOOL disable_edit = FALSE;
  BOOL disable_update = FALSE;
  BOOL disable_alltoread = FALSE;
  BOOL disable_search = FALSE;

  ENTER();

  // dispose the old contextMenu if it still exists
  if(data->contextMenu)
  {
    MUI_DisposeObject(data->contextMenu);
    data->contextMenu = NULL;
  }

  // if this was a RMB click on the titlebar we create our own special menu
  if(m->ontop)
  {
    data->contextMenu = MenustripObject,
      Child, MenuObjectT(tr(MSG_MA_CTX_FOLDERLIST)),
        Child, MenuitemObject, MUIA_Menuitem_Title, tr(MSG_Folder), MUIA_Menuitem_CopyStrings, FALSE, MUIA_UserData, 1, MUIA_Menuitem_Enabled, FALSE, MUIA_Menuitem_Checked, TRUE, MUIA_Menuitem_Checkit, TRUE, MUIA_Menuitem_Toggle, TRUE, End,
        Child, MenuitemObject, MUIA_Menuitem_Title, tr(MSG_Total),  MUIA_Menuitem_CopyStrings, FALSE, MUIA_UserData, 2, MUIA_Menuitem_Checked, hasFColTotal(C->FolderCols), MUIA_Menuitem_Checkit, TRUE, MUIA_Menuitem_Toggle, TRUE, End,
        Child, MenuitemObject, MUIA_Menuitem_Title, tr(MSG_Unread), MUIA_Menuitem_CopyStrings, FALSE, MUIA_UserData, 3, MUIA_Menuitem_Checked, hasFColUnread(C->FolderCols), MUIA_Menuitem_Checkit, TRUE, MUIA_Menuitem_Toggle, TRUE, End,
        Child, MenuitemObject, MUIA_Menuitem_Title, tr(MSG_New),    MUIA_Menuitem_CopyStrings, FALSE, MUIA_UserData, 4, MUIA_Menuitem_Checked, hasFColNew(C->FolderCols), MUIA_Menuitem_Checkit, TRUE, MUIA_Menuitem_Toggle, TRUE, End,
        Child, MenuitemObject, MUIA_Menuitem_Title, tr(MSG_Size),   MUIA_Menuitem_CopyStrings, FALSE, MUIA_UserData, 5, MUIA_Menuitem_Checked, hasFColSize(C->FolderCols), MUIA_Menuitem_Checkit, TRUE, MUIA_Menuitem_Toggle, TRUE, End,
        Child, MenuitemObject, MUIA_Menuitem_Title, NM_BARLABEL, End,
        Child, MenuitemObject, MUIA_Menuitem_Title, tr(MSG_MA_CTX_DEFWIDTH_THIS), MUIA_Menuitem_CopyStrings, FALSE, MUIA_UserData, MUIV_NList_Menu_DefWidth_This, End,
        Child, MenuitemObject, MUIA_Menuitem_Title, tr(MSG_MA_CTX_DEFWIDTH_ALL),  MUIA_Menuitem_CopyStrings, FALSE, MUIA_UserData, MUIV_NList_Menu_DefWidth_All,  End,
        Child, MenuitemObject, MUIA_Menuitem_Title, tr(MSG_MA_CTX_DEFORDER_THIS), MUIA_Menuitem_CopyStrings, FALSE, MUIA_UserData, MUIV_NList_Menu_DefOrder_This, End,
        Child, MenuitemObject, MUIA_Menuitem_Title, tr(MSG_MA_CTX_DEFORDER_ALL),  MUIA_Menuitem_CopyStrings, FALSE, MUIA_UserData, MUIV_NList_Menu_DefOrder_All,  End,
      End,
    End;

    RETURN((IPTR)data->contextMenu);
    return (IPTR)data->contextMenu;
  }

  // Now lets find out which entry is under the mouse pointer
  DoMethod(obj, MUIM_NListtree_TestPos, m->mx, m->my, &r);

  tn = r.tpr_TreeNode;

  if(tn == NULL || tn->tn_User == NULL)
  {
    disable_delete = TRUE;
    disable_edit   = TRUE;
    disable_update = TRUE;
    disable_alltoread = TRUE;
    disable_search = TRUE;
  }
  else
  {
    folder = ((struct FolderNode *)tn->tn_User)->folder;

    // Set this Treenode as activ
    if(tn != (struct MUI_NListtree_TreeNode *)xget(obj, MUIA_NListtree_Active))
      set(obj, MUIA_NListtree_Active, tn);

    // Now we have to set the disabled flag if this is not a custom folder
    if(isDefaultFolder(folder) && !isGroupFolder(folder))
      disable_delete = TRUE;

    if(isGroupFolder(folder))
    {
      disable_update = TRUE;
      disable_search = TRUE;
      disable_alltoread = TRUE;
    }

    if(isSentMailFolder(folder))
      disable_alltoread = TRUE;
  }

  // We create the ContextMenu now
  data->contextMenu = MenustripObject,
    Child, MenuObjectT(folder ? FolderName(folder) : tr(MSG_FOLDER_NONSEL)),
      Child, MenuitemObject, MUIA_Menuitem_Title, tr(MSG_FOLDER_NEWFOLDER),            MUIA_Menuitem_CopyStrings, FALSE, MUIA_UserData, CMN_NEWF,   End,
      Child, MenuitemObject, MUIA_Menuitem_Title, tr(MSG_FOLDER_NEWFOLDERGROUP),       MUIA_Menuitem_CopyStrings, FALSE, MUIA_UserData, CMN_NEWFG,  End,
      Child, MenuitemObject, MUIA_Menuitem_Title, tr(MSG_FOLDER_EDIT),                 MUIA_Menuitem_CopyStrings, FALSE, MUIA_Menuitem_Enabled, !disable_edit,   MUIA_UserData, CMN_EDITF,   End,
      Child, MenuitemObject, MUIA_Menuitem_Title, tr(MSG_FOLDER_DELETE),               MUIA_Menuitem_CopyStrings, FALSE, MUIA_Menuitem_Enabled, !disable_delete, MUIA_UserData, CMN_DELETEF, End,
      Child, MenuitemObject, MUIA_Menuitem_Title, NM_BARLABEL, End,
      Child, MenuitemObject, MUIA_Menuitem_Title, tr(MSG_MA_MSEARCH),                  MUIA_Menuitem_CopyStrings, FALSE, MUIA_Menuitem_Enabled, !disable_search, MUIA_UserData, CMN_SEARCH,  End,
      Child, MenuitemObject, MUIA_Menuitem_Title, tr(MSG_MA_UPDATEINDEX),              MUIA_Menuitem_CopyStrings, FALSE, MUIA_Menuitem_Enabled, !disable_update, MUIA_UserData, CMN_INDEX,   End,
      Child, MenuitemObject, MUIA_Menuitem_Title, NM_BARLABEL, End,
      Child, lastItem = MenuitemObject, MUIA_Menuitem_Title, tr(MSG_FOLDER_ALLTOREAD), MUIA_Menuitem_CopyStrings, FALSE, MUIA_Menuitem_Enabled, !disable_alltoread, MUIA_UserData, CMN_ALLTOREAD, End,
    End,
  End;

  // depending on the folder we have to append some additional
  // menu items or not.
  if(folder != NULL)
  {
    Object *newItem;

    // check if this is the trash folder
    if(isTrashFolder(folder) &&
       (newItem = Menuitem(tr(MSG_MA_REMOVEDELETED), NULL, TRUE, FALSE, CMN_EMPTYTRASH)) != NULL)
    {
      DoMethod(data->contextMenu, MUIM_Family_Insert, newItem, lastItem);
      lastItem = newItem;
    }

    // check if this is the SPAM folder
    if(C->SpamFilterEnabled &&
       (newItem = Menuitem(tr(MSG_MA_REMOVESPAM), NULL, !isGroupFolder(folder), FALSE, CMN_EMPTYSPAM)) != NULL)
    {
      DoMethod(data->contextMenu, MUIM_Family_Insert, newItem, lastItem);
      lastItem = newItem;
    }
  }

  RETURN((IPTR)data->contextMenu);
  return (IPTR)data->contextMenu;
}

///
/// OVERLOAD(MUIM_ContextMenuChoice)
OVERLOAD(MUIM_ContextMenuChoice)
{
  struct MUIP_ContextMenuChoice *m = (struct MUIP_ContextMenuChoice *)msg;
  ULONG result = 0;

  ENTER();

  switch(xget(m->item, MUIA_UserData))
  {
    // if the user selected a TitleContextMenu item
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
    {
      ULONG flag = (1 << (xget(m->item, MUIA_UserData)-1));

      if(isFlagSet(C->FolderCols, flag))
        clearFlag(C->FolderCols, flag);
      else
        setFlag(C->FolderCols, flag);

      DoMethod(obj, METHOD(MakeFormat));
    }
    break;

    // or other item out of the FolderListContextMenu
    case CMN_EDITF:     { DoMethod(obj, METHOD(EditFolder), FALSE); } break;
    case CMN_DELETEF:   { DoMethod(obj, METHOD(DeleteFolder)); } break;
    case CMN_INDEX:     { DoMethod(_app(obj), MUIM_YAMApplication_RebuildFolderIndex); } break;
    case CMN_NEWF:      { DoMethod(obj, METHOD(NewFolder)); } break;
    case CMN_NEWFG:     { DoMethod(obj, METHOD(NewFolderGroup), NULL); } break;
    case CMN_EMPTYTRASH:{ DoMethod(_app(obj), MUIM_YAMApplication_EmptyTrashFolder, FALSE); } break;
    case CMN_EMPTYSPAM: { DoMethod(_app(obj), MUIM_YAMApplication_DeleteSpamMails, FALSE); } break;
    case CMN_ALLTOREAD: { DoMethod(_app(obj), MUIM_CallHook, &MA_SetAllStatusToHook, SFLAG_READ, SFLAG_NEW); } break;
    case CMN_SEARCH:    { DoMethod(_app(obj), MUIM_YAMApplication_OpenSearchMailWindow, GetCurrentFolder()); } break;

    default:
      result = DoSuperMethodA(cl, obj, (Msg)msg);
  }

  RETURN(result);
  return result;
}

///

/* Public Methods */
/// DECLARE(MakeFormat)
// creates format definition for folder listtree
DECLARE(MakeFormat)
{
  static const int defwidth[NUMBER_FOLDERTREE_COLUMNS] = { 100,0,0,0,0 };
  char format[SIZE_LARGE];
  BOOL first = TRUE;
  int i;

  ENTER();

  *format = '\0';

  for(i = 0; i < NUMBER_FOLDERTREE_COLUMNS; i++)
  {
    if(isFlagSet(C->FolderCols, (1<<i)))
    {
      int p;

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

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

      if(i > 0)
        strlcat(format, " P=\033r", sizeof(format));
      else
        strlcat(format, " PCS=C", sizeof(format));
    }
  }
  strlcat(format, " TBAR", sizeof(format));

  SHOWSTRING(DBF_GUI, format);

  // set the new NList_Format to our object
  set(obj, MUIA_NList_Format, format);

  RETURN(0);
  return 0;
}

///
/// DECLARE(ChangeFolder)
// set the clicked folder as the current one
DECLARE(ChangeFolder) // struct MUI_NListtree_TreeNode *treenode
{
  struct MUI_NListtree_TreeNode *tn = msg->treenode;

  ENTER();

  // check the treenode and its user data, this method may be invoked with a NULL treenode
  if(tn != NULL && tn->tn_User != NULL)
  {
    struct FolderNode *fnode = (struct FolderNode *)tn->tn_User;

    SetCurrentFolder(fnode->folder);
    MA_ChangeFolder(NULL, FALSE);
  }

  RETURN(0);
  return 0;
}

///
/// DECLARE(NewFolderGroup)
// creates a new folder group
DECLARE(NewFolderGroup) // char *name
{
  struct Folder folder;
  ULONG result = FALSE;

  ENTER();

  InitFolder(&folder, FT_GROUP);

  // either use the supplied name or prompt the user to enter one otherwise
  if(msg->name != NULL)
  {
    strlcpy(folder.Name, msg->name, sizeof(folder.Name));
  }
  else
  {
    if(StringRequest(folder.Name, sizeof(folder.Name), tr(MSG_FO_NEWFGROUP), tr(MSG_FO_NEWFGROUPREQ), tr(MSG_Okay), NULL, tr(MSG_Cancel), FALSE, _win(obj)) == 0)
      folder.Name[0] = '\0';
  }

  if(folder.Name[0] != '\0')
  {
    struct FolderNode *fnode;

    LockFolderList(G->folders);
    fnode = AddNewFolderNode(G->folders, memdup(&folder, sizeof(folder)));
    UnlockFolderList(G->folders);

    if(fnode != NULL)
    {
      // remember the backlink to our own folder node
      fnode->folder->self = fnode;

      // insert the new folder node and remember its treenode pointer
      if((fnode->folder->Treenode = (struct MUI_NListtree_TreeNode *)DoMethod(obj, MUIM_NListtree_Insert, folder.Name, fnode, MUIV_NListtree_Insert_ListNode_Root, MUIV_NListtree_Insert_PrevNode_Tail, TNF_LIST | TNF_OPEN)) != NULL)
        set(obj, MUIA_NListtree_Active, fnode->folder->Treenode);

      result = FO_SaveTree();
    }
  }

  RETURN(result);
  return result;
}

///
/// DECLARE(NewFolder)
// creates a new folder
DECLARE(NewFolder)
{
  GETDATA;
  int mode;
  BOOL openEditWindow = TRUE;

  ENTER();

  // call MUI_Request() first
  mode = MUI_Request(_app(obj), _win(obj), MUIF_NONE, tr(MSG_MA_NewFolder), tr(MSG_FO_NewFolderGads), tr(MSG_FO_NewFolderReq));

  // reset the folder struct and set some default values
  InitFolder(&data->newFolder, FT_CUSTOM);

  switch (mode)
  {
    case 1: break;
    case 2:
    {
      // as the user decided to use the settings from the current folder, we copy
      // the current one to our new one.
      memcpy(&data->newFolder, GetCurrentFolder(), sizeof(data->newFolder));

      if(isGroupFolder(&data->newFolder))
      {
        DoMethod(obj, METHOD(NewFolderGroup), NULL);
        openEditWindow = FALSE;
      }
      else
      {
        if(isIncomingFolder(&data->newFolder) || isTrashFolder(&data->newFolder) || isDraftsFolder(&data->newFolder) || isSpamFolder(&data->newFolder) || isArchiveFolder(&data->newFolder))
          data->newFolder.Type = FT_CUSTOM;
        else if(isOutgoingFolder(&data->newFolder) || isSentFolder(&data->newFolder))
          data->newFolder.Type = FT_CUSTOMSENT;

        // now that we have the correct folder type, we set some default values for the new
        // folder
        data->newFolder.Path[0] = '\0';
        data->newFolder.Name[0] = '\0';
        data->newFolder.imageObject = NULL;
        // erase the message list which might have been copied from the current folder
        data->newFolder.messages = NULL;
        // no image for the folder by default
        data->newFolder.ImageIndex = -1;
      }
    }
    break;

    case 3:
    {
      char foldersPath[SIZE_PATHFILE];
      struct FileReqCache *frc;

      CreateFilename("Folders", foldersPath, sizeof(foldersPath));
      if((frc = ReqFile(ASL_FOLDER, _win(obj), tr(MSG_FO_SelectDir), REQF_DRAWERSONLY, foldersPath, "")) != NULL)
      {
        strlcpy(data->newFolder.Path, frc->drawer, sizeof(data->newFolder.Path));

        FO_LoadConfig(&data->newFolder);
      }
      else
      {
        openEditWindow = FALSE;
      }
    }
    break;

    default:
    {
      openEditWindow = FALSE;
    }
  }

  if(openEditWindow == TRUE)
  {
    // there is no "old" folder which could be edited, just the new one
    DoMethod(obj, METHOD(OpenFolderEditWindow), NULL);
  }

  RETURN(0);
  return 0;
}

///
/// DECLARE(EditFolder)
//  Opens folder window to edit the settings of the active folder
DECLARE(EditFolder) // ULONG wasDoubleClick
{
  GETDATA;

  ENTER();

  // respect the configuration about editing on double click
  if(msg->wasDoubleClick == FALSE || C->FolderDoubleClick == TRUE)
  {
    struct Folder *folder = GetCurrentFolder();

    // copy the current folder as this will be used for editing
    memcpy(&data->newFolder, GetCurrentFolder(), sizeof(data->newFolder));
    if(isGroupFolder(folder))
    {
      // don't edit folder groups on double click
      // a double click is used to fold/unfold the group
      if(msg->wasDoubleClick == FALSE)
      {
        if(StringRequest(folder->Name, SIZE_NAME, tr(MSG_FO_EDIT_FGROUP), tr(MSG_FO_EDIT_FGROUPREQ), tr(MSG_Okay), NULL, tr(MSG_Cancel), FALSE, _win(obj)))
          DoMethod(obj, MUIM_NListtree_Redraw, MUIV_NListtree_Redraw_Active, MUIF_NONE);
      }
    }
    else
    {
      DoMethod(obj, METHOD(OpenFolderEditWindow), folder);
    }
  }

  RETURN(0);
  return 0;
}

///
/// DECLARE(OpenFolderEditWindow)
// create and open a new folder edit window if it does exist yet
DECLARE(OpenFolderEditWindow) // struct Folder *folder
{
  GETDATA;

  ENTER();

  if(data->folderEditWindow == NULL)
  {
    Object *folderEditWindow;

    if((folderEditWindow = FolderEditWindowObject, End) != NULL)
    {
      if(SafeOpenWindow(folderEditWindow) == TRUE)
      {
        data->folderEditWindow = folderEditWindow;
        // dispose the folder edit window whenever it asks for it
        DoMethod(folderEditWindow, MUIM_Notify, MUIA_FolderEditWindow_DisposeRequest, MUIV_EveryTime, obj, 2, METHOD(CloseFolderEditWindow), FALSE);
      }
      else
      {
        // the folder edit window adds itself to the application, hence it is not
        // enough to just dispose the object
        DoMethod(_app(obj), MUIM_YAMApplication_DisposeWindow, folderEditWindow);
      }
    }
  }

  if(data->folderEditWindow != NULL)
  {
    // set the folder to be edited, the old folder might be NULL in case of a new folder
    xset(data->folderEditWindow,
      MUIA_FolderEditWindow_OldFolder, msg->folder,
      MUIA_FolderEditWindow_EditFolder, &data->newFolder);
  }

  RETURN(0);
  return 0;
}

///
/// DECLARE(CloseFolderEditWindow)
// dispose the folder edit window
DECLARE(CloseFolderEditWindow) // ULONG immediately
{
  GETDATA;

  ENTER();

  if(data->folderEditWindow != NULL)
  {
    if(msg->immediately == TRUE)
    {
      DoMethod(_app(obj), MUIM_YAMApplication_DisposeWindow, data->folderEditWindow);
    }
    else
    {
      // don't dispose the window directly here, because this method is called
      // as a direct notification of the window's close request and disposing
      // it immediately would "pull the rug out" from under the window.
      DoMethod(_app(obj), MUIM_Application_PushMethod, _app(obj), 2, MUIM_YAMApplication_DisposeWindow, data->folderEditWindow);
    }
    data->folderEditWindow = NULL;
  }

  RETURN(0);
  return 0;
}

///
/// DECLARE(DeleteFolder)
// removes the active folder
DECLARE(DeleteFolder)
{
  struct Folder *folder;
  struct FolderNode *fnode;
  BOOL delete_folder = FALSE;

  ENTER();

  folder = GetCurrentFolder();
  fnode = (struct FolderNode *)folder->Treenode->tn_User;

  switch(folder->Type)
  {
    case FT_CUSTOM:
    case FT_CUSTOMSENT:
    case FT_CUSTOMMIXED:
    case FT_ARCHIVE:
    {
      if(MUI_Request(_app(obj), _win(obj), MUIF_NONE, NULL, tr(MSG_YesNoReq2), tr(MSG_CO_ConfirmDelete)) != 0)
      {
        // check if the folder that is about to be deleted is part
        // of an active filter and if so remove it from it
        if(FolderIsUsedByFilters(folder) == TRUE)
          RemoveFolderFromFilters(folder);

        // Here we dispose the folderimage Object because the destructor
        // of the Folder Listtree can't do this without throwing enforcer hits
        if(folder->imageObject != NULL)
        {
          // we make sure that the NList also doesn't use the image in future anymore
          DoMethod(obj, MUIM_NList_UseImage, NULL, folder->ImageIndex, MUIF_NONE);

          // and last, but not least we free the BC object here, so that this Object is also gone
          MUI_DisposeObject(folder->imageObject);
          folder->imageObject = NULL; // let's set it to NULL so that the destructor doesn't do the work again.
        }

        delete_folder = TRUE;
        DeleteMailDir(folder->Fullpath, FALSE);
      }
    }
    break;

    case FT_GROUP:
    {
      struct MUI_NListtree_TreeNode *tn_sub;
      struct MUI_NListtree_TreeNode *tn_group = folder->Treenode;

      // check if the active treenode is a list and if it is empty
      // we have to do this like the following because there is no other way to
      // get known if the active entry has subentries.
      if((tn_sub = (struct MUI_NListtree_TreeNode *)DoMethod(obj, MUIM_NListtree_GetEntry, tn_group, MUIV_NListtree_GetEntry_Position_Head, MUIF_NONE)) != NULL)
      {
        // Now we popup a requester and if this requester is confirmed we move the subentries to the parent node.
        if(MUI_Request(_app(obj), _win(obj), MUIF_NONE, NULL, tr(MSG_YesNoReq2), tr(MSG_FO_GROUP_CONFDEL)))
        {
          struct MUI_NListtree_TreeNode *tn_sub_next = tn_sub;

          delete_folder = TRUE;

          set(obj, MUIA_NListtree_Quiet, TRUE);

          while(tn_sub_next != NULL)
          {
            tn_sub_next = (struct MUI_NListtree_TreeNode *)DoMethod(obj, MUIM_NListtree_GetEntry, tn_sub, MUIV_NListtree_GetEntry_Position_Next, MUIV_NListtree_GetEntry_Flag_SameLevel);

            // move entry to the parent of the group
            DoMethod(obj, MUIM_NListtree_Move, tn_group, tn_sub, MUIV_NListtree_Move_NewListNode_Active, MUIV_NListtree_Move_NewTreeNode_Tail, MUIF_NONE);

            tn_sub = tn_sub_next;
          }

          set(obj, MUIA_NListtree_Quiet, FALSE);
        }
      }
      else
        delete_folder = TRUE;
    }
    break;

    default:
      DisplayBeep(_screen(obj));
    break;
  }

  if(delete_folder == TRUE)
  {
    D(DBF_FOLDER, "deleting folder '%s'", folder->Name);

    // remove the entry from the listtree now
    DoMethod(obj, MUIM_NListtree_Remove, MUIV_NListtree_Remove_ListNode_Root, MUIV_NListtree_Remove_TreeNode_Active, MUIF_NONE);

    // save the Tree to the folder config now
    FO_SaveTree();

    // remove the folder from the global folder list
    LockFolderList(G->folders);
    RemoveFolderNode(G->folders, fnode);
    UnlockFolderList(G->folders);

    // finally free all the memory
    DeleteFolderNode(fnode);
    FreeFolder(folder);

    // update the statistics in case the just deleted folder contained new or unread mail
    DisplayStatistics(NULL, TRUE);

    // trigger a changed folder tree
    set(obj, ATTR(TreeChanged), TRUE);
  }
  else
    D(DBF_FOLDER, "keeping folder '%s'", folder->Name);

  RETURN(0);
  return 0;
}

///