[SHIMGVW] Fix cursor (#1471)
[reactos.git] / dll / win32 / shimgvw / shimgvw.c
index 1c1ea76..46553b3 100644 (file)
@@ -1,17 +1,15 @@
 /*
- *
  * PROJECT:         ReactOS Picture and Fax Viewer
  * FILE:            dll/win32/shimgvw/shimgvw.c
  * PURPOSE:         shimgvw.dll
- * PROGRAMMER:      Dmitry Chapyshev (dmitry@reactos.org)
- *
- * UPDATE HISTORY:
- *      28/05/2008  Created
+ * PROGRAMMERS:     Dmitry Chapyshev (dmitry@reactos.org)
+ *                  Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
  */
 
 #define WIN32_NO_STATUS
 #define _INC_WINDOWS
 #define COM_NO_WINDOWS_H
+#define INITGUID
 
 #include <stdarg.h>
 
 #include <winnls.h>
 #include <winreg.h>
 #include <wingdi.h>
+#include <windowsx.h>
 #include <objbase.h>
 #include <commctrl.h>
 #include <commdlg.h>
 #include <gdiplus.h>
 #include <tchar.h>
 #include <strsafe.h>
+#include <shlwapi.h>
 
 #define NDEBUG
 #include <debug.h>
 
 #include "shimgvw.h"
 
-
 HINSTANCE hInstance;
 SHIMGVW_SETTINGS shiSettings;
+SHIMGVW_FILENODE *currentFile;
 GpImage *image;
 WNDPROC PrevProc = NULL;
 
 HWND hDispWnd, hToolBar;
 
+/* zooming */
+#define MIN_ZOOM 10
+#define MAX_ZOOM 1600
+UINT ZoomPercents = 100;
+static const UINT ZoomSteps[] =
+{
+    10, 25, 50, 100, 200, 400, 800, 1600
+};
+
+/* animation */
+UINT            m_nFrameIndex = 0;
+UINT            m_nFrameCount = 0;
+UINT            m_nLoopIndex = 0;
+UINT            m_nLoopCount = (UINT)-1;
+PropertyItem   *m_pDelayItem = NULL;
+
+#define ANIME_TIMER_ID  9999
+
+static void Anime_FreeInfo(void)
+{
+    if (m_pDelayItem)
+    {
+        free(m_pDelayItem);
+        m_pDelayItem = NULL;
+    }
+    m_nFrameIndex = 0;
+    m_nFrameCount = 0;
+    m_nLoopIndex = 0;
+    m_nLoopCount = (UINT)-1;
+}
+
+static BOOL Anime_LoadInfo(void)
+{
+    GUID *dims;
+    UINT nDimCount = 0;
+    UINT cbItem;
+    UINT result;
+    PropertyItem *pItem;
+
+    Anime_FreeInfo();
+    KillTimer(hDispWnd, ANIME_TIMER_ID);
+
+    if (!image)
+        return FALSE;
+
+    GdipImageGetFrameDimensionsCount(image, &nDimCount);
+    if (nDimCount)
+    {
+        dims = (GUID *)calloc(nDimCount, sizeof(GUID));
+        if (dims)
+        {
+            GdipImageGetFrameDimensionsList(image, dims, nDimCount);
+            GdipImageGetFrameCount(image, dims, &result);
+            m_nFrameCount = result;
+            free(dims);
+        }
+    }
+
+    result = 0;
+    GdipGetPropertyItemSize(image, PropertyTagFrameDelay, &result);
+    cbItem = result;
+    if (cbItem)
+    {
+        m_pDelayItem = (PropertyItem *)malloc(cbItem);
+        GdipGetPropertyItem(image, PropertyTagFrameDelay, cbItem, m_pDelayItem);
+    }
+
+    result = 0;
+    GdipGetPropertyItemSize(image, PropertyTagLoopCount, &result);
+    cbItem = result;
+    if (cbItem)
+    {
+        pItem = (PropertyItem *)malloc(cbItem);
+        if (pItem)
+        {
+            if (GdipGetPropertyItem(image, PropertyTagLoopCount, cbItem, pItem) == Ok)
+            {
+                m_nLoopCount = *(WORD *)pItem->value;
+            }
+            free(pItem);
+        }
+    }
+
+    if (m_pDelayItem)
+    {
+        SetTimer(hDispWnd, ANIME_TIMER_ID, 0, NULL);
+    }
+
+    return m_pDelayItem != NULL;
+}
+
+static void Anime_SetFrameIndex(UINT nFrameIndex)
+{
+    if (nFrameIndex < m_nFrameCount)
+    {
+        GUID guid = FrameDimensionTime;
+        if (Ok != GdipImageSelectActiveFrame(image, &guid, nFrameIndex))
+        {
+            guid = FrameDimensionPage;
+            GdipImageSelectActiveFrame(image, &guid, nFrameIndex);
+        }
+    }
+    m_nFrameIndex = nFrameIndex;
+}
+
+DWORD Anime_GetFrameDelay(UINT nFrameIndex)
+{
+    if (nFrameIndex < m_nFrameCount && m_pDelayItem)
+    {
+        return ((DWORD *)m_pDelayItem->value)[m_nFrameIndex] * 10;
+    }
+    return 0;
+}
+
+BOOL Anime_Step(DWORD *pdwDelay)
+{
+    *pdwDelay = INFINITE;
+    if (m_nLoopCount == (UINT)-1)
+        return FALSE;
+
+    if (m_nFrameIndex + 1 < m_nFrameCount)
+    {
+        *pdwDelay = Anime_GetFrameDelay(m_nFrameIndex);
+        Anime_SetFrameIndex(m_nFrameIndex);
+        ++m_nFrameIndex;
+        return TRUE;
+    }
+
+    if (m_nLoopCount == 0 || m_nLoopIndex < m_nLoopCount)
+    {
+        *pdwDelay = Anime_GetFrameDelay(m_nFrameIndex);
+        Anime_SetFrameIndex(m_nFrameIndex);
+        m_nFrameIndex = 0;
+        ++m_nLoopIndex;
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+static void ZoomInOrOut(BOOL bZoomIn)
+{
+    INT i;
+
+    if (bZoomIn)    /* zoom in */
+    {
+        /* find next step */
+        for (i = 0; i < ARRAYSIZE(ZoomSteps); ++i)
+        {
+            if (ZoomPercents < ZoomSteps[i])
+                break;
+        }
+        if (i == ARRAYSIZE(ZoomSteps))
+            ZoomPercents = MAX_ZOOM;
+        else
+            ZoomPercents = ZoomSteps[i];
+
+        /* update tool bar buttons */
+        SendMessage(hToolBar, TB_ENABLEBUTTON, IDC_ZOOMM, TRUE);
+        if (ZoomPercents >= MAX_ZOOM)
+            SendMessage(hToolBar, TB_ENABLEBUTTON, IDC_ZOOMP, FALSE);
+        else
+            SendMessage(hToolBar, TB_ENABLEBUTTON, IDC_ZOOMP, TRUE);
+    }
+    else            /* zoom out */
+    {
+        /* find previous step */
+        for (i = ARRAYSIZE(ZoomSteps); i > 0; )
+        {
+            --i;
+            if (ZoomSteps[i] < ZoomPercents)
+                break;
+        }
+        if (i < 0)
+            ZoomPercents = MIN_ZOOM;
+        else
+            ZoomPercents = ZoomSteps[i];
+
+        /* update tool bar buttons */
+        SendMessage(hToolBar, TB_ENABLEBUTTON, IDC_ZOOMP, TRUE);
+        if (ZoomPercents <= MIN_ZOOM)
+            SendMessage(hToolBar, TB_ENABLEBUTTON, IDC_ZOOMM, FALSE);
+        else
+            SendMessage(hToolBar, TB_ENABLEBUTTON, IDC_ZOOMM, TRUE);
+    }
+
+    /* redraw */
+    InvalidateRect(hDispWnd, NULL, TRUE);
+}
+
+static void ResetZoom(void)
+{
+    RECT Rect;
+    UINT ImageWidth, ImageHeight;
+
+    /* get disp window size and image size */
+    GetClientRect(hDispWnd, &Rect);
+    GdipGetImageWidth(image, &ImageWidth);
+    GdipGetImageHeight(image, &ImageHeight);
+
+    /* compare two aspect rates. same as
+       (ImageHeight / ImageWidth < Rect.bottom / Rect.right) in real */
+    if (ImageHeight * Rect.right < Rect.bottom * ImageWidth)
+    {
+        if (Rect.right < ImageWidth)
+        {
+            /* it's large, shrink it */
+            ZoomPercents = (Rect.right * 100) / ImageWidth;
+        }
+        else
+        {
+            /* it's small. show as original size */
+            ZoomPercents = 100;
+        }
+    }
+    else
+    {
+        if (Rect.bottom < ImageHeight)
+        {
+            /* it's large, shrink it */
+            ZoomPercents = (Rect.bottom * 100) / ImageHeight;
+        }
+        else
+        {
+            /* it's small. show as original size */
+            ZoomPercents = 100;
+        }
+    }
+}
+
 /* ToolBar Buttons */
 static const TBBUTTON Buttons [] =
 {   /* iBitmap,     idCommand,   fsState,         fsStyle,     bReserved[2], dwData, iString */
@@ -58,17 +288,27 @@ static const TBBUTTON Buttons [] =
 
 static void pLoadImage(LPWSTR szOpenFileName)
 {
+    /* check file presence */
     if (GetFileAttributesW(szOpenFileName) == 0xFFFFFFFF)
     {
         DPRINT1("File %s not found!\n", szOpenFileName);
         return;
     }
 
+    /* load now */
     GdipLoadImageFromFile(szOpenFileName, &image);
     if (!image)
     {
         DPRINT1("GdipLoadImageFromFile() failed\n");
+        return;
     }
+    Anime_LoadInfo();
+
+    /* reset zoom */
+    ResetZoom();
+
+    /* redraw */
+    InvalidateRect(hDispWnd, NULL, TRUE);
 }
 
 static void pSaveImageAs(HWND hwnd)
@@ -76,25 +316,14 @@ static void pSaveImageAs(HWND hwnd)
     OPENFILENAMEW sfn;
     ImageCodecInfo *codecInfo;
     WCHAR szSaveFileName[MAX_PATH];
-    WCHAR szFilterMask[2048];
+    WCHAR *szFilterMask;
     GUID rawFormat;
     UINT num;
     UINT size;
-    UINT sizeRemain;
+    size_t sizeRemain;
     UINT j;
     WCHAR *c;
 
-    ZeroMemory(szSaveFileName, sizeof(szSaveFileName));
-    ZeroMemory(szFilterMask, sizeof(szFilterMask));
-    ZeroMemory(&sfn, sizeof(sfn));
-    sfn.lStructSize = sizeof(sfn);
-    sfn.hwndOwner   = hwnd;
-    sfn.hInstance   = hInstance;
-    sfn.lpstrFile   = szSaveFileName;
-    sfn.lpstrFilter = szFilterMask;
-    sfn.nMaxFile    = MAX_PATH;
-    sfn.Flags       = OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;
-
     GdipGetImageEncodersSize(&num, &size);
     codecInfo = malloc(size);
     if (!codecInfo)
@@ -106,7 +335,36 @@ static void pSaveImageAs(HWND hwnd)
     GdipGetImageEncoders(num, size, codecInfo);
     GdipGetImageRawFormat(image, &rawFormat);
 
-    sizeRemain = sizeof(szFilterMask);
+    sizeRemain = 0;
+
+    for (j = 0; j < num; ++j)
+    {
+        // Every pair needs space for the Description, twice the Extensions, 1 char for the space, 2 for the braces and 2 for the NULL terminators.
+        sizeRemain = sizeRemain + (((wcslen(codecInfo[j].FormatDescription) + (wcslen(codecInfo[j].FilenameExtension) * 2) + 5) * sizeof(WCHAR)));
+    }
+
+    /* Add two more chars for the last terminator */
+    sizeRemain = sizeRemain + (sizeof(WCHAR) * 2);
+
+    szFilterMask = malloc(sizeRemain);
+    if (!szFilterMask)
+    {
+        DPRINT1("cannot allocate memory for filter mask in pSaveImageAs()");
+        free(codecInfo);
+        return;
+    }
+
+    ZeroMemory(szSaveFileName, sizeof(szSaveFileName));
+    ZeroMemory(szFilterMask, sizeRemain);
+    ZeroMemory(&sfn, sizeof(sfn));
+    sfn.lStructSize = sizeof(sfn);
+    sfn.hwndOwner   = hwnd;
+    sfn.hInstance   = hInstance;
+    sfn.lpstrFile   = szSaveFileName;
+    sfn.lpstrFilter = szFilterMask;
+    sfn.nMaxFile    = MAX_PATH;
+    sfn.Flags       = OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;
+
     c = szFilterMask;
 
     for (j = 0; j < num; ++j)
@@ -123,7 +381,7 @@ static void pSaveImageAs(HWND hwnd)
         c++;
         sizeRemain -= sizeof(*c);
 
-        if (IsEqualGUID(&rawFormat, &codecInfo[j].FormatID) == TRUE)
+        if (IsEqualGUID(&rawFormat, &codecInfo[j].FormatID) != FALSE)
         {
             sfn.nFilterIndex = j + 1;
         }
@@ -131,24 +389,208 @@ static void pSaveImageAs(HWND hwnd)
 
     if (GetSaveFileNameW(&sfn))
     {
-        if (GdipSaveImageToFile(image, szSaveFileName, &codecInfo[sfn.nFilterIndex - 1].Clsid, NULL) != Ok)
+        if (m_pDelayItem)
         {
-            DPRINT1("GdipSaveImageToFile() failed\n");
+            /* save animation */
+            KillTimer(hDispWnd, ANIME_TIMER_ID);
+
+            DPRINT1("FIXME: save animation\n");
+            if (GdipSaveImageToFile(image, szSaveFileName, &codecInfo[sfn.nFilterIndex - 1].Clsid, NULL) != Ok)
+            {
+                DPRINT1("GdipSaveImageToFile() failed\n");
+            }
+
+            SetTimer(hDispWnd, ANIME_TIMER_ID, 0, NULL);
+        }
+        else
+        {
+            /* save non-animation */
+            if (GdipSaveImageToFile(image, szSaveFileName, &codecInfo[sfn.nFilterIndex - 1].Clsid, NULL) != Ok)
+            {
+                DPRINT1("GdipSaveImageToFile() failed\n");
+            }
         }
     }
 
+    free(szFilterMask);
     free(codecInfo);
 }
 
+static VOID
+pLoadImageFromNode(SHIMGVW_FILENODE *node, HWND hwnd)
+{
+    WCHAR szTitleBuf[800];
+    WCHAR szResStr[512];
+    WCHAR *c;
+
+    if (node)
+    {
+        c = wcsrchr(node->FileName, '\\');
+        if (c)
+        {
+            c++;
+        }
+
+        LoadStringW(hInstance, IDS_APPTITLE, szResStr, ARRAYSIZE(szResStr));
+        StringCbPrintfW(szTitleBuf, sizeof(szTitleBuf), L"%ls%ls%ls", szResStr, L" - ", c);
+        SetWindowTextW(hwnd, szTitleBuf);
+
+        if (image)
+        {
+            GdipDisposeImage(image);
+        }
+
+        pLoadImage(node->FileName);
+    }
+}
+
+static SHIMGVW_FILENODE*
+pBuildFileList(LPWSTR szFirstFile)
+{
+    HANDLE hFindHandle;
+    WCHAR *extension;
+    WCHAR szSearchPath[MAX_PATH];
+    WCHAR szSearchMask[MAX_PATH];
+    WCHAR szFileTypes[MAX_PATH];
+    WIN32_FIND_DATAW findData;
+    SHIMGVW_FILENODE *currentNode = NULL;
+    SHIMGVW_FILENODE *root = NULL;
+    SHIMGVW_FILENODE *conductor = NULL;
+    ImageCodecInfo *codecInfo;
+    UINT num;
+    UINT size;
+    UINT j;
+
+    StringCbCopyW(szSearchPath, sizeof(szSearchPath), szFirstFile);
+    PathRemoveFileSpecW(szSearchPath);
+
+    GdipGetImageDecodersSize(&num, &size);
+    codecInfo = malloc(size);
+    if (!codecInfo)
+    {
+        DPRINT1("malloc() failed in pLoadFileList()\n");
+        return NULL;
+    }
+
+    GdipGetImageDecoders(num, size, codecInfo);
+
+    root = malloc(sizeof(SHIMGVW_FILENODE));
+    if (!root)
+    {
+        DPRINT1("malloc() failed in pLoadFileList()\n");
+        free(codecInfo);
+        return NULL;
+    }
+
+    conductor = root;
+
+    for (j = 0; j < num; ++j)
+    {
+        StringCbCopyW(szFileTypes, sizeof(szFileTypes), codecInfo[j].FilenameExtension);
+
+        extension = wcstok(szFileTypes, L";");
+        while (extension != NULL)
+        {
+            PathCombineW(szSearchMask, szSearchPath, extension);
+
+            hFindHandle = FindFirstFileW(szSearchMask, &findData);
+            if (hFindHandle != INVALID_HANDLE_VALUE)
+            {
+                do
+                {
+                    PathCombineW(conductor->FileName, szSearchPath, findData.cFileName);
+
+                    // compare the name of the requested file with the one currently found.
+                    // if the name matches, the current node is returned by the function.
+                    if (wcscmp(szFirstFile, conductor->FileName) == 0)
+                    {
+                        currentNode = conductor;
+                    }
+
+                    conductor->Next = malloc(sizeof(SHIMGVW_FILENODE));
+
+                    // if malloc fails, make circular what we have and return it
+                    if (!conductor->Next)
+                    {
+                        DPRINT1("malloc() failed in pLoadFileList()\n");
+
+                        conductor->Next = root;
+                        root->Prev = conductor;
+
+                        FindClose(hFindHandle);
+                        free(codecInfo);
+                        return conductor;
+                    }
+
+                    conductor->Next->Prev = conductor;
+                    conductor = conductor->Next;
+                }
+                while (FindNextFileW(hFindHandle, &findData) != 0);
+
+                FindClose(hFindHandle);
+            }
+
+            extension = wcstok(NULL, L";");
+        }
+    }
+
+    // we now have a node too much in the list. In case the requested file was not found,
+    // we use this node to store the name of it, otherwise we free it.
+    if (currentNode == NULL)
+    {
+        StringCchCopyW(conductor->FileName, MAX_PATH, szFirstFile);
+        currentNode = conductor;
+    }
+    else
+    {
+        conductor = conductor->Prev;
+        free(conductor->Next);
+    }
+
+    // link the last node with the first one to make the list circular
+    conductor->Next = root;
+    root->Prev = conductor;
+    conductor = currentNode;
+
+    free(codecInfo);
+
+    return conductor;
+}
+
+static VOID
+pFreeFileList(SHIMGVW_FILENODE *root)
+{
+    SHIMGVW_FILENODE *conductor;
+
+    root->Prev->Next = NULL;
+    root->Prev = NULL;
+
+    while (root)
+    {
+        conductor = root;
+        root = conductor->Next;
+        free(conductor);
+    }
+}
+
+static VOID
+ImageView_UpdateWindow(HWND hwnd)
+{
+    InvalidateRect(hwnd, NULL, FALSE);
+    UpdateWindow(hwnd);
+}
+
 static VOID
 ImageView_DrawImage(HWND hwnd)
 {
     GpGraphics *graphics;
-    UINT uImgWidth, uImgHeight;
-    UINT height = 0, width = 0, x = 0, y = 0;
+    UINT ImageWidth, ImageHeight;
+    INT ZoomedWidth, ZoomedHeight, x, y;
     PAINTSTRUCT ps;
-    RECT rect;
+    RECT rect, margin;
     HDC hdc;
+    HBRUSH white;
+    HGDIOBJ hbrOld;
 
     hdc = BeginPaint(hwnd, &ps);
     if (!hdc)
@@ -164,76 +606,56 @@ ImageView_DrawImage(HWND hwnd)
         return;
     }
 
-    GdipGetImageWidth(image, &uImgWidth);
-    GdipGetImageHeight(image, &uImgHeight);
+    GdipGetImageWidth(image, &ImageWidth);
+    GdipGetImageHeight(image, &ImageHeight);
 
     if (GetClientRect(hwnd, &rect))
     {
-        FillRect(hdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
-
-        if ((rect.right == uImgWidth)&&(rect.bottom == uImgHeight))
+        ZoomedWidth = (ImageWidth * ZoomPercents) / 100;
+        ZoomedHeight = (ImageHeight * ZoomPercents) / 100;
+
+        x = (rect.right - ZoomedWidth) / 2;
+        y = (rect.bottom - ZoomedHeight) / 2;
+
+        white = GetStockObject(WHITE_BRUSH);
+        // Fill top part
+        margin = rect;
+        margin.bottom = y - 1;
+        FillRect(hdc, &margin, white);
+        // Fill bottom part
+        margin.top = y + ZoomedHeight + 1;
+        margin.bottom = rect.bottom;
+        FillRect(hdc, &margin, white);
+        // Fill left part
+        margin.top = y - 1;
+        margin.bottom = y + ZoomedHeight + 1;
+        margin.right = x - 1;
+        FillRect(hdc, &margin, white);
+        // Fill right part
+        margin.left = x + ZoomedWidth + 1;
+        margin.right = rect.right;
+        FillRect(hdc, &margin, white);
+
+        DPRINT("x = %d, y = %d, ImageWidth = %u, ImageHeight = %u\n");
+        DPRINT("rect.right = %ld, rect.bottom = %ld\n", rect.right, rect.bottom);
+        DPRINT("ZoomPercents = %d, ZoomedWidth = %d, ZoomedHeight = %d\n",
+               ZoomPercents, ZoomedWidth, ZoomedWidth);
+
+        if (ZoomPercents % 100 == 0)
         {
-            x = 0, y = 0, width = rect.right, height = rect.bottom;
+            GdipSetInterpolationMode(graphics, InterpolationModeNearestNeighbor);
+            GdipSetSmoothingMode(graphics, SmoothingModeNone);
         }
-        else if ((rect.right >= uImgWidth)&&(rect.bottom >= uImgHeight))
+        else
         {
-            x = (rect.right/2)-(uImgWidth/2);
-            y = (rect.bottom/2)-(uImgHeight/2);
-            width = uImgWidth;
-            height = uImgHeight;
-        }
-        else if ((rect.right < uImgWidth)||(rect.bottom < uImgHeight))
-        {
-            if (rect.bottom < uImgHeight)
-            {
-                height = rect.bottom;
-                width = uImgWidth*(UINT)rect.bottom/uImgHeight;
-                x = (rect.right/2)-(width/2);
-                y = (rect.bottom/2)-(height/2);
-            }
-            if (rect.right < uImgWidth)
-            {
-                width = rect.right;
-                height = uImgHeight*(UINT)rect.right/uImgWidth;
-                x = (rect.right/2)-(width/2);
-                y = (rect.bottom/2)-(height/2);
-            }
-            if ((height > rect.bottom)||(width > rect.right))
-            {
-                for (;;)
-                {
-                    if (((int)width - 1 < 0)||((int)height - 1 < 0)) break;
-                    width -= 1;
-                    height -= 1;
-                    y = (rect.bottom/2)-(height/2);
-                    x = (rect.right/2)-(width/2);
-                    if ((height < rect.bottom)&&(width < rect.right)) break;
-                }
-            }
+            GdipSetInterpolationMode(graphics, InterpolationModeHighQualityBilinear);
+            GdipSetSmoothingMode(graphics, SmoothingModeHighQuality);
         }
-        else if ((rect.right <= uImgWidth)&&(rect.bottom <= uImgHeight))
-        {
-            height = uImgHeight*(UINT)rect.right/uImgWidth;
-            y = (rect.bottom/2)-(height/2);
-            width = rect.right;
 
-            if ((height > rect.bottom)||(width > rect.right))
-            {
-                for (;;)
-                {
-                    if (((int)width - 1 < 0)||((int)height - 1 < 0)) break;
-                    width -= 1;
-                    height -= 1;
-                    y = (rect.bottom/2)-(height/2);
-                    x = (rect.right/2)-(width/2);
-                    if ((height < rect.bottom)&&(width < rect.right)) break;
-                }
-            }
-        }
-
-        DPRINT("x = %d\ny = %d\nWidth = %d\nHeight = %d\n\nrect.right = %d\nrect.bottom = %d\n\nuImgWidth = %d\nuImgHeight = %d\n", x, y, width, height, rect.right, rect.bottom, uImgWidth, uImgHeight);
-        Rectangle(hdc, x - 1, y - 1, x + width + 1, y + height + 1);
-        GdipDrawImageRect(graphics, image, x, y, width, height);
+        hbrOld = SelectObject(hdc, GetStockObject(NULL_BRUSH));
+        Rectangle(hdc, x - 1, y - 1, x + ZoomedWidth + 1, y + ZoomedHeight + 1);
+        SelectObject(hdc, hbrOld);
+        GdipDrawImageRectI(graphics, image, x, y, ZoomedWidth, ZoomedHeight);
     }
     GdipDeleteGraphics(graphics);
     EndPaint(hwnd, &ps);
@@ -343,6 +765,19 @@ ImageView_CreateToolBar(HWND hwnd)
     return FALSE;
 }
 
+static void ImageView_OnTimer(HWND hwnd)
+{
+    DWORD dwDelay;
+
+    KillTimer(hwnd, ANIME_TIMER_ID);
+    InvalidateRect(hwnd, NULL, FALSE);
+
+    if (Anime_Step(&dwDelay))
+    {
+        SetTimer(hwnd, ANIME_TIMER_ID, dwDelay, NULL);
+    }
+}
+
 LRESULT CALLBACK
 ImageView_DispWndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
 {
@@ -353,7 +788,16 @@ ImageView_DispWndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
             ImageView_DrawImage(hwnd);
             return 0L;
         }
-       }
+        case WM_TIMER:
+        {
+            if (wParam == ANIME_TIMER_ID)
+            {
+                ImageView_OnTimer(hwnd);
+                return 0;
+            }
+            break;
+        }
+    }
     return CallWindowProc(PrevProc, hwnd, Message, wParam, lParam);
 }
 
@@ -366,16 +810,25 @@ ImageView_InitControls(HWND hwnd)
 
     if (shiSettings.Maximized) ShowWindow(hwnd, SW_MAXIMIZE);
 
-    hDispWnd = CreateWindowEx(0, _T("STATIC"), _T(""),
+    hDispWnd = CreateWindowEx(0, WC_STATIC, _T(""),
                               WS_CHILD | WS_VISIBLE,
                               0, 0, 0, 0, hwnd, NULL, hInstance, NULL);
 
     SetClassLongPtr(hDispWnd, GCL_STYLE, CS_HREDRAW | CS_VREDRAW);
-    PrevProc = (WNDPROC) SetWindowLongPtr(hDispWnd, GWL_WNDPROC, (LPARAM) ImageView_DispWndProc);
+    PrevProc = (WNDPROC) SetWindowLongPtr(hDispWnd, GWLP_WNDPROC, (LPARAM) ImageView_DispWndProc);
 
     ImageView_CreateToolBar(hwnd);
 }
 
+static VOID
+ImageView_OnMouseWheel(HWND hwnd, INT x, INT y, INT zDelta, UINT fwKeys)
+{
+    if (zDelta != 0)
+    {
+        ZoomInOrOut(zDelta > 0);
+    }
+}
+
 LRESULT CALLBACK
 ImageView_WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
 {
@@ -386,21 +839,47 @@ ImageView_WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
             ImageView_InitControls(hwnd);
             return 0L;
         }
+
+        case WM_KEYDOWN:
+            switch (LOWORD(wParam))
+            {
+                case VK_LEFT:
+                    PostMessage(hwnd, WM_COMMAND, MAKEWPARAM(IDC_PREV, BN_CLICKED), (LPARAM)NULL);
+                    break;
+
+                case VK_RIGHT:
+                    PostMessage(hwnd, WM_COMMAND, MAKEWPARAM(IDC_NEXT, BN_CLICKED), (LPARAM)NULL);
+                    break;
+            }
+            break;
+
         case WM_COMMAND:
         {
             switch (wParam)
             {
                 case IDC_PREV:
+                {
+                    currentFile = currentFile->Prev;
+                    pLoadImageFromNode(currentFile, hwnd);
+                }
 
                 break;
                 case IDC_NEXT:
+                {
+                    currentFile = currentFile->Next;
+                    pLoadImageFromNode(currentFile, hwnd);
+                }
 
                 break;
                 case IDC_ZOOMP:
-
+                {
+                    ZoomInOrOut(TRUE);
+                }
                 break;
                 case IDC_ZOOMM:
-
+                {
+                    ZoomInOrOut(FALSE);
+                }
                 break;
                 case IDC_SAVE:
                     pSaveImageAs(hwnd);
@@ -410,15 +889,29 @@ ImageView_WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
 
                 break;
                 case IDC_ROT1:
+                {
+                    GdipImageRotateFlip(image, Rotate270FlipNone);
+                    ImageView_UpdateWindow(hwnd);
+                }
 
                 break;
                 case IDC_ROT2:
+                {
+                    GdipImageRotateFlip(image, Rotate90FlipNone);
+                    ImageView_UpdateWindow(hwnd);
+                }
 
                 break;
             }
         }
         break;
 
+        case WM_MOUSEWHEEL:
+            ImageView_OnMouseWheel(hwnd,
+                GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam),
+                (SHORT)HIWORD(wParam), (UINT)LOWORD(wParam));
+            break;
+
         case WM_NOTIFY:
         {
             LPNMHDR pnmhdr = (LPNMHDR)lParam;
@@ -432,6 +925,7 @@ ImageView_WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
 
                     lpttt = (LPTOOLTIPTEXT)lParam;
                     idButton = (UINT)lpttt->hdr.idFrom;
+                    lpttt->hinst = hInstance;
 
                     switch (idButton)
                     {
@@ -479,14 +973,20 @@ ImageView_WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
         {
             RECT rc;
             SendMessage(hToolBar, TB_AUTOSIZE, 0, 0);
-            SendMessage(hToolBar, TB_GETITEMRECT, 1, (LPARAM)&rc);
-            MoveWindow(hDispWnd, 1, 1, LOWORD(lParam)-1, HIWORD(lParam)-rc.bottom, TRUE);
+            GetWindowRect(hToolBar, &rc);
+            MoveWindow(hDispWnd, 1, 1, LOWORD(lParam) - 1, HIWORD(lParam) - (rc.bottom - rc.top) - 1, TRUE);
+            /* is it maximized or restored? */
+            if (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED)
+            {
+                /* reset zoom */
+                ResetZoom();
+            }
             return 0L;
         }
         case WM_DESTROY:
         {
             ImageView_SaveSettings(hwnd);
-            SetWindowLongPtr(hDispWnd, GWL_WNDPROC, (LPARAM) PrevProc);
+            SetWindowLongPtr(hDispWnd, GWLP_WNDPROC, (LPARAM) PrevProc);
             PostQuitMessage(0);
             break;
         }
@@ -502,6 +1002,7 @@ ImageView_CreateWindow(HWND hwnd, LPWSTR szFileName)
     ULONG_PTR gdiplusToken;
     WNDCLASS WndClass = {0};
     TCHAR szBuf[512];
+    WCHAR szInitialFile[MAX_PATH];
     HWND hMainWnd;
     MSG msg;
 
@@ -529,8 +1030,8 @@ ImageView_CreateWindow(HWND hwnd, LPWSTR szFileName)
     WndClass.hInstance      = hInstance;
     WndClass.style          = CS_HREDRAW | CS_VREDRAW;
     WndClass.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPICON));
-    WndClass.hCursor        = LoadCursor(hInstance, IDC_ARROW);
-    WndClass.hbrBackground  = (HBRUSH)COLOR_WINDOW;
+    WndClass.hCursor        = LoadCursor(NULL, IDC_ARROW);
+    WndClass.hbrBackground  = NULL;   /* less flicker */
 
     if (!RegisterClass(&WndClass)) return -1;
 
@@ -540,6 +1041,16 @@ ImageView_CreateWindow(HWND hwnd, LPWSTR szFileName)
                             CW_USEDEFAULT, CW_USEDEFAULT,
                             0, 0, NULL, NULL, hInstance, NULL);
 
+    // make sure the path has no quotes on it
+    wcscpy(szInitialFile, szFileName);
+    PathUnquoteSpacesW(szInitialFile);
+
+    currentFile = pBuildFileList(szInitialFile);
+    if (currentFile)
+    {
+        pLoadImageFromNode(currentFile, hMainWnd);
+    }
+
     // Show it
     ShowWindow(hMainWnd, SW_SHOW);
     UpdateWindow(hMainWnd);
@@ -551,8 +1062,13 @@ ImageView_CreateWindow(HWND hwnd, LPWSTR szFileName)
         DispatchMessageW(&msg);
     }
 
+    pFreeFileList(currentFile);
+
     if (image)
         GdipDisposeImage(image);
+
+    Anime_FreeInfo();
+
     GdiplusShutdown(gdiplusToken);
     return -1;
 }
@@ -613,4 +1129,3 @@ DllMain(IN HINSTANCE hinstDLL,
 
     return TRUE;
 }
-