Git conversion: Make reactos the root directory, move rosapps, rostests, wallpapers...
[reactos.git] / base / shell / progman / group.c
diff --git a/base/shell/progman/group.c b/base/shell/progman/group.c
new file mode 100644 (file)
index 0000000..918681a
--- /dev/null
@@ -0,0 +1,674 @@
+/*
+ * Program Manager
+ *
+ * Copyright 1996 Ulrich Schmid
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/*
+ * PROJECT:         ReactOS Program Manager
+ * COPYRIGHT:       GPL - See COPYING in the top level directory
+ * FILE:            base/shell/progman/group.c
+ * PURPOSE:         Program group files helper functions
+ * PROGRAMMERS:     Ulrich Schmid
+ *                  Hermes Belusca-Maito (hermes.belusca@sfr.fr)
+ */
+
+#include "progman.h"
+
+/***********************************************************************
+ *
+ * UX Theming helpers, dropped from msconfig_new/comctl32ex/uxthemesupp.c
+ *
+ */
+
+static HMODULE hUxTheme = NULL;
+
+typedef HRESULT (WINAPI* ETDTProc)(HWND, DWORD);
+static ETDTProc fnEnableThemeDialogTexture = NULL;
+
+typedef HRESULT (WINAPI* SWTProc)(HWND, LPCWSTR, LPCWSTR);
+static SWTProc fnSetWindowTheme = NULL;
+
+
+static BOOL
+InitUxTheme(VOID)
+{
+    if (hUxTheme) return TRUE;
+
+    hUxTheme = LoadLibraryW(L"uxtheme.dll");
+    if (hUxTheme == NULL) return FALSE;
+
+    fnEnableThemeDialogTexture =
+        (ETDTProc)GetProcAddress(hUxTheme, "EnableThemeDialogTexture");
+    fnSetWindowTheme =
+        (SWTProc)GetProcAddress(hUxTheme, "SetWindowTheme");
+
+    return TRUE;
+}
+
+#if 0
+static VOID
+CleanupUxTheme(VOID)
+{
+    FreeLibrary(hUxTheme);
+    hUxTheme = NULL;
+}
+#endif
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Taken from WinSpy++ 1.7
+// http://www.catch22.net/software/winspy
+// Copyright (c) 2002 by J Brown
+//
+
+HRESULT
+WINAPI
+EnableThemeDialogTexture(_In_ HWND  hwnd,
+                         _In_ DWORD dwFlags)
+{
+    if (!InitUxTheme())
+        return HRESULT_FROM_WIN32(GetLastError());
+
+    if (!fnEnableThemeDialogTexture)
+        return HRESULT_FROM_WIN32(GetLastError());
+
+    return fnEnableThemeDialogTexture(hwnd, dwFlags);
+}
+
+HRESULT
+WINAPI
+SetWindowTheme(_In_ HWND    hwnd,
+               _In_ LPCWSTR pszSubAppName,
+               _In_ LPCWSTR pszSubIdList)
+{
+    if (!InitUxTheme())
+        return HRESULT_FROM_WIN32(GetLastError());
+
+    if (!fnSetWindowTheme)
+        return HRESULT_FROM_WIN32(GetLastError());
+
+    return fnSetWindowTheme(hwnd, pszSubAppName, pszSubIdList);
+}
+
+
+/***********************************************************************
+ *
+ *           GROUP_GroupWndProc
+ */
+
+static
+LRESULT
+CALLBACK
+GROUP_GroupWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+    PROGGROUP* group;
+    INT iItem;
+    LVITEMW lvItem;
+    POINT pt;
+
+    group = (PROGGROUP*)GetWindowLongPtrW(hWnd, 0);
+
+    switch (uMsg)
+    {
+        case WM_NCCREATE:
+        {
+            LPCREATESTRUCTW pcs = (LPCREATESTRUCTW)lParam;
+            LPMDICREATESTRUCTW pMDIcs = (LPMDICREATESTRUCTW)pcs->lpCreateParams;
+            group = (PROGGROUP*)pMDIcs->lParam;
+            SetWindowLongPtrW(hWnd, 0, (LONG_PTR)group);
+
+            if (group->bIsCommonGroup)
+            {
+                DefMDIChildProcW(hWnd, WM_SETICON, ICON_BIG,
+                                 (LPARAM)CopyImage(Globals.hCommonGroupIcon,
+                                                   IMAGE_ICON,
+                                                   GetSystemMetrics(SM_CXICON),
+                                                   GetSystemMetrics(SM_CYICON),
+                                                   LR_COPYFROMRESOURCE));
+                DefMDIChildProcW(hWnd, WM_SETICON, ICON_SMALL,
+                                 (LPARAM)CopyImage(Globals.hCommonGroupIcon,
+                                                   IMAGE_ICON,
+                                                   GetSystemMetrics(SM_CXSMICON),
+                                                   GetSystemMetrics(SM_CYSMICON),
+                                                   LR_COPYFROMRESOURCE));
+            }
+            else
+            {
+                DefMDIChildProcW(hWnd, WM_SETICON, ICON_BIG,
+                                 (LPARAM)CopyImage(Globals.hPersonalGroupIcon,
+                                                   IMAGE_ICON,
+                                                   GetSystemMetrics(SM_CXICON),
+                                                   GetSystemMetrics(SM_CYICON),
+                                                   LR_COPYFROMRESOURCE));
+                DefMDIChildProcW(hWnd, WM_SETICON, ICON_SMALL,
+                                 (LPARAM)CopyImage(Globals.hPersonalGroupIcon,
+                                                   IMAGE_ICON,
+                                                   GetSystemMetrics(SM_CXSMICON),
+                                                   GetSystemMetrics(SM_CYSMICON),
+                                                   LR_COPYFROMRESOURCE));
+            }
+            break;
+        }
+
+        case WM_NCDESTROY:
+            SetWindowLongPtrW(hWnd, 0, 0);
+            break;
+
+        case WM_CREATE:
+        {
+            DWORD dwStyle;
+            RECT rect;
+            GetClientRect(hWnd, &rect);
+            group->hListView = CreateWindowW(WC_LISTVIEW,
+                                             NULL,
+                                             WS_CHILD | WS_VISIBLE | WS_OVERLAPPED,
+                                             0, 0,
+                                             rect.right - rect.left,
+                                             rect.bottom - rect.top,
+                                             hWnd,
+                                             NULL,
+                                             Globals.hInstance,
+                                             NULL);
+            dwStyle = (GetWindowLongPtrW(group->hListView, GWL_STYLE) | LVS_SHOWSELALWAYS) & ~LVS_AUTOARRANGE;
+            SetWindowLongPtrW(group->hListView, GWL_STYLE, dwStyle);
+            dwStyle = SendMessageA(group->hListView, LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0) | LVS_EX_BORDERSELECT;
+            SendMessageA(group->hListView, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_SNAPTOGRID, dwStyle);
+            InitUxTheme();
+            SetWindowTheme(group->hListView, L"Explorer", NULL);
+            group->hListLarge = ImageList_Create(GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), ILC_COLOR24 | ILC_MASK, 1, 1);
+            SendMessageA(group->hListView, LVM_SETIMAGELIST, 0, (LPARAM)group->hListLarge);
+            SendMessageA(group->hListView, LVM_SETICONSPACING, 0, MAKELPARAM(80, 64));
+            break;
+        }
+
+        case WM_DESTROY:
+        {
+            SendMessageA(group->hListView, LVM_SETIMAGELIST, 0, 0);
+            ImageList_Destroy(group->hListLarge);
+            DestroyWindow(group->hListView);
+            break;
+        }
+
+        case WM_SIZE:
+        {
+            RECT rect;
+            rect.left = 0;
+            rect.top  = 0;
+            rect.right  = LOWORD(lParam);
+            rect.bottom = HIWORD(lParam);
+            AdjustWindowRectEx(&rect, GetWindowLongPtrW(group->hListView, GWL_STYLE), FALSE, GetWindowLongPtrW(group->hListView, GWL_EXSTYLE));
+            MoveWindow(group->hListView, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE);
+            break;
+        }
+
+        case WM_CLOSE:
+            SendMessageW(hWnd, WM_SYSCOMMAND, SC_MINIMIZE, 0);
+            break;
+
+        case WM_SYSCOMMAND:
+            if (wParam == SC_CLOSE) wParam = SC_MINIMIZE;
+            break;
+
+        case WM_CHILDACTIVATE:
+        case WM_NCLBUTTONDOWN:
+            Globals.hActiveGroup = (PROGGROUP*)GetWindowLongPtrW(hWnd, 0);
+            Globals.hActiveGroup->hActiveProgram = NULL;
+            break;
+
+        case WM_NOTIFY:
+            switch (((LPNMHDR)lParam)->code)
+            {
+                case NM_CLICK:
+                {
+                    iItem = ((LPNMITEMACTIVATE)lParam)->iItem;
+                    if (iItem == -1)
+                    {
+                        group->hActiveProgram = NULL;
+                        break;
+                    }
+
+                    lvItem.mask  = LVIF_PARAM;
+                    lvItem.iItem = iItem;
+                    SendMessageW(group->hListView, LVM_GETITEMW, 0, (LPARAM)&lvItem);
+                    group->hActiveProgram = (PROGRAM*)lvItem.lParam;
+                    break;
+                }
+
+                case NM_DBLCLK:
+                {
+                    iItem = ((LPNMITEMACTIVATE)lParam)->iItem;
+                    if (iItem == -1)
+                        break;
+
+                    lvItem.mask  = LVIF_PARAM;
+                    lvItem.iItem = iItem;
+                    SendMessageW(group->hListView, LVM_GETITEMW, 0, (LPARAM)&lvItem);
+                    /* ... or use group->hActiveProgram */
+                    PROGRAM_ExecuteProgram((PROGRAM*)lvItem.lParam);
+                    break;
+                }
+
+                case LVN_BEGINDRAG:
+                {
+                    POINT ptMin;
+
+                    BOOL bFirst = TRUE;
+                    for (iItem = SendMessageA(group->hListView, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
+                         iItem != -1;
+                         iItem = SendMessageA(group->hListView, LVM_GETNEXTITEM, iItem, LVNI_SELECTED))
+                    {
+                        if (bFirst)
+                        {
+                            group->hDragImageList = (HIMAGELIST)SendMessageA(group->hListView,
+                                                                             LVM_CREATEDRAGIMAGE,
+                                                                             iItem,
+                                                                             (LPARAM)&pt);
+                            ptMin  = pt;
+                            bFirst = FALSE;
+                        }
+                        else
+                        {
+                            HIMAGELIST hOneImageList, hTempImageList;
+
+                            hOneImageList = (HIMAGELIST)SendMessageA(group->hListView,
+                                                                     LVM_CREATEDRAGIMAGE,
+                                                                     iItem,
+                                                                     (LPARAM)&pt);
+                            hTempImageList = ImageList_Merge(group->hDragImageList,
+                                                             0,
+                                                             hOneImageList,
+                                                             0,
+                                                             pt.x - ptMin.x,
+                                                             pt.y - ptMin.y);
+                            ImageList_Destroy(group->hDragImageList);
+                            ImageList_Destroy(hOneImageList);
+                            group->hDragImageList = hTempImageList;
+                            ptMin.x = min(ptMin.x, pt.x);
+                            ptMin.y = min(ptMin.y, pt.y);
+                        }
+                    }
+                    // pt = ((LPNMLISTVIEW)lParam)->ptAction;
+                    pt.x = ((LPNMLISTVIEW)lParam)->ptAction.x;
+                    pt.y = ((LPNMLISTVIEW)lParam)->ptAction.y;
+                    group->ptStart = pt;
+                    pt.x -= ptMin.x;
+                    pt.y -= ptMin.y;
+                    ImageList_BeginDrag(group->hDragImageList, 0, pt.x, pt.y);
+                    MapWindowPoints(group->hListView, Globals.hMDIWnd, &pt, 1);
+                    ImageList_DragEnter(Globals.hMDIWnd, pt.x, pt.y);
+                    group->bDragging = TRUE;
+                    group->hOldCursor = GetCursor();
+                    SetCapture(group->hWnd);
+
+                    break;
+                }
+            }
+            break;
+
+        case WM_MOUSEMOVE:
+            if (group->bDragging)
+            {
+                pt.x = GET_X_LPARAM(lParam);
+                pt.y = GET_Y_LPARAM(lParam);
+                MapWindowPoints(group->hWnd, Globals.hMDIWnd, &pt, 1);
+                ImageList_DragMove(pt.x, pt.y);
+            }
+            break;
+
+        case WM_LBUTTONUP:
+            if (group->bDragging)
+            {
+                // LVHITTESTINFO lvhti;
+                POINT ptHit;
+
+                group->bDragging = FALSE;
+                ImageList_DragLeave(Globals.hMDIWnd);
+                ImageList_EndDrag();
+                ImageList_Destroy(group->hDragImageList);
+                ReleaseCapture();
+                SetCursor(group->hOldCursor);
+                ptHit.x = GET_X_LPARAM(lParam);
+                ptHit.y = GET_Y_LPARAM(lParam);
+                MapWindowPoints(group->hWnd, group->hListView, &ptHit, 1);
+                for (iItem = SendMessageA(group->hListView, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
+                     iItem != -1;
+                     iItem = SendMessageA(group->hListView, LVM_GETNEXTITEM, iItem, LVNI_SELECTED))
+                {
+                    SendMessageA(group->hListView, LVM_GETITEMPOSITION, iItem, (LPARAM)&pt);
+                    pt.x += ptHit.x - group->ptStart.x;
+                    pt.y += ptHit.y - group->ptStart.y;
+                    SendMessageA(group->hListView, LVM_SETITEMPOSITION, iItem, MAKELPARAM(pt.x, pt.y));
+                }
+            }
+            break;
+    }
+
+    return DefMDIChildProcW(hWnd, uMsg, wParam, lParam);
+}
+
+/***********************************************************************
+ *
+ *           GROUP_RegisterGroupWinClass
+ */
+
+ATOM GROUP_RegisterGroupWinClass(VOID)
+{
+    WNDCLASSW wndClass;
+
+    wndClass.style         = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
+    wndClass.lpfnWndProc   = GROUP_GroupWndProc;
+    wndClass.cbClsExtra    = 0;
+    wndClass.cbWndExtra    = sizeof(LONG_PTR);
+    wndClass.hInstance     = Globals.hInstance;
+    wndClass.hIcon         = LoadIconW(Globals.hInstance, MAKEINTRESOURCEW(IDI_GROUP_ICON));
+    wndClass.hCursor       = LoadCursorW(NULL, MAKEINTRESOURCEW(IDC_ARROW));
+    wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
+    wndClass.lpszMenuName  = NULL;
+    wndClass.lpszClassName = STRING_GROUP_WIN_CLASS_NAME;
+
+    return RegisterClassW(&wndClass);
+}
+
+/***********************************************************************
+ *
+ *           GROUP_NewGroup
+ */
+
+VOID GROUP_NewGroup(GROUPFORMAT format, BOOL bIsCommonGroup)
+{
+    HANDLE hFile;
+    WCHAR szGrpFile[MAX_PATHNAME_LEN] = L"";
+    WCHAR szTitle[MAX_PATHNAME_LEN]   = L"";
+
+    // ZeroMemory(szTitle, sizeof(szTitle));
+    // ZeroMemory(szGrpFile, sizeof(szGrpFile));
+
+    if (!DIALOG_GroupAttributes(format, szTitle, szGrpFile, MAX_PATHNAME_LEN))
+        return;
+
+    /*
+     * Just check whether the group file does exist. If it does, close the handle, because GRPFILE_ReadGroupFile will
+     * reopen the file for loading. If it doesn't exist, we create a new one.
+     */
+    hFile = CreateFileW(szGrpFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+    if (hFile == INVALID_HANDLE_VALUE)
+    {
+        /* File doesn't exist */
+        PROGGROUP* hGroup = GROUP_AddGroup(format, bIsCommonGroup, szTitle, szGrpFile,
+                                           DEF_GROUP_WIN_XPOS, DEF_GROUP_WIN_YPOS,
+                                           DEF_GROUP_WIN_XPOS + DEF_GROUP_WIN_WIDTH, DEF_GROUP_WIN_YPOS + DEF_GROUP_WIN_HEIGHT,
+                                           0, 0, SW_SHOWNORMAL, 0, 0, FALSE, FALSE);
+        if (hGroup)
+            GRPFILE_WriteGroupFile(hGroup);
+    }
+    else
+    {
+        /* File exist */
+        CloseHandle(hFile);
+        GRPFILE_ReadGroupFile(szGrpFile, bIsCommonGroup);
+    }
+
+    /* FIXME Update progman.ini */
+}
+
+/***********************************************************************
+ *
+ *           GROUP_AddGroup
+ */
+
+PROGGROUP*
+GROUP_AddGroup(GROUPFORMAT format, BOOL bIsCommonGroup, LPCWSTR lpszName, LPCWSTR lpszGrpFile,
+               INT left, INT top, INT right, INT bottom, INT xMin, INT yMin, INT nCmdShow,
+               WORD cxIcon, WORD cyIcon, BOOL bOverwriteFileOk,
+               /* FIXME shouldn't be necessary */
+               BOOL bSuppressShowWindow)
+{
+    PROGGROUP* hGroup;
+    PROGGROUP* hPrior;
+    PROGGROUP** p;
+    LPWSTR hName;
+    LPWSTR hGrpFile;
+    LPCWSTR GroupFileName;
+    INT skip;
+    INT width;
+    INT height;
+    INT seqnum;
+    MDICREATESTRUCTW mcs;
+    WINDOWPLACEMENT WndPl;
+
+    WndPl.length = sizeof(WndPl);
+
+    // FIXME: Use system default position in case we don't place the window at a given (x,y) coordinate.
+
+    if (bIsCommonGroup)
+    {
+        if (swscanf(lpszGrpFile,
+                    L"%d %d %d %d %d %d %d %n",
+                    &WndPl.rcNormalPosition.left,
+                    &WndPl.rcNormalPosition.top,
+                    &WndPl.rcNormalPosition.right,
+                    &WndPl.rcNormalPosition.bottom,
+                    &WndPl.ptMinPosition.x,
+                    &WndPl.ptMinPosition.y,
+                    &WndPl.showCmd,
+                    &skip) == 7)
+        {
+            WndPl.flags = WPF_SETMINPOSITION;
+            width  = WndPl.rcNormalPosition.right  - WndPl.rcNormalPosition.left;
+            height = WndPl.rcNormalPosition.bottom - WndPl.rcNormalPosition.top;
+            GroupFileName = &lpszGrpFile[skip];
+        }
+        else
+        {
+#if 0 // FIXME!
+            WndPl.rcNormalPosition.top    = CW_USEDEFAULT;
+            WndPl.rcNormalPosition.left   = CW_USEDEFAULT;
+            WndPl.rcNormalPosition.right  = 0;
+            WndPl.rcNormalPosition.bottom = 0;
+            width  = CW_USEDEFAULT;
+            height = CW_USEDEFAULT;
+            WndPl.showCmd = SW_SHOWNORMAL;
+            GroupFileName = lpszGrpFile;
+#else
+        WndPl.flags = WPF_SETMINPOSITION;
+        WndPl.ptMinPosition.x = xMin;
+        WndPl.ptMinPosition.y = yMin;
+        WndPl.rcNormalPosition.left   = left;
+        WndPl.rcNormalPosition.top    = top;
+        WndPl.rcNormalPosition.right  = right;
+        WndPl.rcNormalPosition.bottom = bottom;
+        width  = right  - left;
+        height = bottom - top;
+        WndPl.showCmd = nCmdShow;
+        GroupFileName = lpszGrpFile;
+#endif
+        }
+    }
+    else
+    {
+        WndPl.flags = WPF_SETMINPOSITION;
+        WndPl.ptMinPosition.x = xMin;
+        WndPl.ptMinPosition.y = yMin;
+        WndPl.rcNormalPosition.left   = left;
+        WndPl.rcNormalPosition.top    = top;
+        WndPl.rcNormalPosition.right  = right;
+        WndPl.rcNormalPosition.bottom = bottom;
+        width  = right  - left;
+        height = bottom - top;
+        WndPl.showCmd = nCmdShow;
+        GroupFileName = lpszGrpFile;
+    }
+
+    hGroup   = Alloc(HEAP_ZERO_MEMORY, sizeof(*hGroup));
+    hName    = Alloc(HEAP_ZERO_MEMORY, (wcslen(lpszName)      + 1) * sizeof(WCHAR));
+    hGrpFile = Alloc(HEAP_ZERO_MEMORY, (wcslen(GroupFileName) + 1) * sizeof(WCHAR));
+    if (!hGroup || !hName || !hGrpFile)
+    {
+        MAIN_MessageBoxIDS(IDS_OUT_OF_MEMORY, IDS_ERROR, MB_OK);
+        if (hGroup)   Free(hGroup);
+        if (hName)    Free(hName);
+        if (hGrpFile) Free(hGrpFile);
+        return NULL;
+    }
+    memcpy(hName   , lpszName     , (wcslen(lpszName)      + 1) * sizeof(WCHAR));
+    memcpy(hGrpFile, GroupFileName, (wcslen(GroupFileName) + 1) * sizeof(WCHAR));
+
+    Globals.hActiveGroup = hGroup;
+
+    seqnum = 1;
+    hPrior = NULL;
+    for (p = &Globals.hGroups; *p; p = &hPrior->hNext)
+    {
+        hPrior = *p;
+        if (hPrior->seqnum >= seqnum)
+            seqnum = hPrior->seqnum + 1;
+    }
+    *p = hGroup;
+
+    hGroup->hPrior           = hPrior;
+    hGroup->hNext            = NULL;
+    hGroup->format           = format;
+    hGroup->bIsCommonGroup   = bIsCommonGroup;
+    hGroup->hName            = hName;
+    hGroup->hGrpFile         = hGrpFile;
+    hGroup->bOverwriteFileOk = bOverwriteFileOk;
+    hGroup->seqnum           = seqnum;
+    hGroup->nCmdShow         = nCmdShow;
+#if 0
+    hGroup->x         = x;
+    hGroup->y         = y;
+    hGroup->width     = width;
+    hGroup->height    = height;
+#endif
+    hGroup->iconx            = cxIcon;
+    hGroup->icony            = cyIcon;
+    hGroup->hPrograms        = NULL;
+    hGroup->hActiveProgram   = NULL;
+    hGroup->TagsSize         = 0;
+    hGroup->Tags             = NULL;
+
+    mcs.szClass = STRING_GROUP_WIN_CLASS_NAME;
+    mcs.szTitle = lpszName;
+    mcs.hOwner  = NULL;
+    mcs.x       = WndPl.rcNormalPosition.left;
+    mcs.y       = WndPl.rcNormalPosition.top;
+    mcs.cx      = width;
+    mcs.cy      = height;
+    mcs.style   = 0;
+    mcs.lParam  = (LPARAM)hGroup;
+
+    hGroup->hWnd = (HWND)SendMessageW(Globals.hMDIWnd, WM_MDICREATE, 0, (LPARAM)&mcs);
+
+    SetWindowPlacement(hGroup->hWnd, &WndPl);
+
+#if 1
+    if (!bSuppressShowWindow) /* FIXME shouldn't be necessary */
+#endif
+        UpdateWindow(hGroup->hWnd);
+
+    return hGroup;
+}
+
+
+
+
+
+/***********************************************************************
+ *
+ *           GROUP_ModifyGroup
+ */
+
+VOID GROUP_ModifyGroup(PROGGROUP* hGroup)
+{
+    WCHAR Dest[MAX_PATHNAME_LEN]; // szName
+    WCHAR szGrpFile[MAX_PATHNAME_LEN]; // szFile
+
+    wcsncpy(Dest, hGroup->hName, ARRAYSIZE(Dest));
+    wcsncpy(szGrpFile, hGroup->hGrpFile, ARRAYSIZE(szGrpFile));
+
+    if (!DIALOG_GroupAttributes(hGroup->format, Dest, szGrpFile, MAX_PATHNAME_LEN))
+        return;
+
+    if (wcscmp(szGrpFile, hGroup->hGrpFile))
+        hGroup->bOverwriteFileOk = FALSE;
+
+    MAIN_ReplaceString(&hGroup->hName, Dest);
+    MAIN_ReplaceString(&hGroup->hGrpFile, szGrpFile);
+
+    GRPFILE_WriteGroupFile(hGroup);
+
+    /* FIXME Delete old GrpFile if GrpFile changed */
+
+    /* FIXME Update progman.ini */
+
+    SetWindowTextW(hGroup->hWnd, Dest);
+}
+
+/***********************************************************************
+ *
+ *           GROUP_DeleteGroup
+ */
+
+VOID GROUP_DeleteGroup(PROGGROUP* hGroup)
+{
+    if (Globals.hActiveGroup == hGroup)
+        Globals.hActiveGroup = NULL;
+
+    if (hGroup->hPrior)
+        hGroup->hPrior->hNext = hGroup->hNext;
+    else
+        Globals.hGroups = hGroup->hNext;
+
+    if (hGroup->hNext)
+        hGroup->hNext->hPrior = hGroup->hPrior;
+
+    while (hGroup->hPrograms)
+        PROGRAM_DeleteProgram(hGroup->hPrograms, FALSE);
+
+    /* FIXME Update progman.ini */
+
+    SendMessageW(Globals.hMDIWnd, WM_MDIDESTROY, (WPARAM)hGroup->hWnd, 0);
+
+    if (hGroup->Tags)
+        Free(hGroup->Tags);
+    Free(hGroup->hName);
+    Free(hGroup->hGrpFile);
+    Free(hGroup);
+}
+
+/***********************************************************************
+ *
+ *           GROUP_ShowGroupWindow
+ */
+
+/* FIXME shouldn't be necessary */
+VOID GROUP_ShowGroupWindow(PROGGROUP* hGroup)
+{
+    ShowWindow(hGroup->hWnd, hGroup->nCmdShow);
+    UpdateWindow(hGroup->hWnd);
+}
+
+/***********************************************************************
+ *
+ *           GROUP_ActiveGroup
+ */
+
+PROGGROUP* GROUP_ActiveGroup(VOID)
+{
+    return Globals.hActiveGroup;
+}