aboutsummaryrefslogtreecommitdiff
path: root/Src/Plugins/Library/ml_devices
diff options
context:
space:
mode:
Diffstat (limited to 'Src/Plugins/Library/ml_devices')
-rw-r--r--Src/Plugins/Library/ml_devices/backBuffer.cpp276
-rw-r--r--Src/Plugins/Library/ml_devices/backBuffer.h64
-rw-r--r--Src/Plugins/Library/ml_devices/common.h76
-rw-r--r--Src/Plugins/Library/ml_devices/config.cpp119
-rw-r--r--Src/Plugins/Library/ml_devices/config.h44
-rw-r--r--Src/Plugins/Library/ml_devices/deviceCommands.cpp207
-rw-r--r--Src/Plugins/Library/ml_devices/deviceCommands.h24
-rw-r--r--Src/Plugins/Library/ml_devices/deviceHandler.cpp206
-rw-r--r--Src/Plugins/Library/ml_devices/deviceHandler.h56
-rw-r--r--Src/Plugins/Library/ml_devices/deviceManagerHandler.cpp174
-rw-r--r--Src/Plugins/Library/ml_devices/deviceManagerHandler.h54
-rw-r--r--Src/Plugins/Library/ml_devices/embeddedEditor.cpp1240
-rw-r--r--Src/Plugins/Library/ml_devices/embeddedEditor.h79
-rw-r--r--Src/Plugins/Library/ml_devices/eventRelay.cpp465
-rw-r--r--Src/Plugins/Library/ml_devices/eventRelay.h116
-rw-r--r--Src/Plugins/Library/ml_devices/fillRegion.cpp134
-rw-r--r--Src/Plugins/Library/ml_devices/fillRegion.h51
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/common.h76
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/device.cpp871
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/device.h114
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceActivity.cpp519
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceActivity.h91
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceCommandNodeParser.cpp75
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceCommandNodeParser.h39
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceCommandParser.cpp192
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceCommandParser.h47
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceConnectionNodeParser.cpp75
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceConnectionNodeParser.h39
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceConnectionParser.cpp181
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceConnectionParser.h46
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceIconEditor.cpp178
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceIconEditor.h22
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceNodeParser.cpp75
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceNodeParser.h39
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceParser.cpp306
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceParser.h54
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceTypeNodeParser.cpp75
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceTypeNodeParser.h39
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceTypeParser.cpp182
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceTypeParser.h46
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceView.cpp994
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceView.h15
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/gen_deviceprovider.slnbin0 -> 2938 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/gen_deviceprovider.vcproj415
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/gen_deviceprovider.vcxproj335
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/gen_deviceprovider.vcxproj.filters164
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/iconStore.cpp289
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/iconStore.h53
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/main.cpp2
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/main.h31
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/plugin.cpp114
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/plugin.h21
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/provider.cpp336
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/provider.h50
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/resource.h39
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/resources.rc186
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/stringBuilder.cpp85
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/stringBuilder.h31
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/strings.cpp243
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/strings.h82
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/supportedCommand.cpp108
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/supportedCommand.h46
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/testSuite.cpp555
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/testSuite.h67
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/testSuiteLoader.cpp226
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/testSuiteLoader.h60
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/testprovider.xml244
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/wasabi.cpp139
-rw-r--r--Src/Plugins/Library/ml_devices/gen_deviceprovider/wasabi.h57
-rw-r--r--Src/Plugins/Library/ml_devices/graphics.cpp313
-rw-r--r--Src/Plugins/Library/ml_devices/graphics.h72
-rw-r--r--Src/Plugins/Library/ml_devices/image.cpp978
-rw-r--r--Src/Plugins/Library/ml_devices/image.h227
-rw-r--r--Src/Plugins/Library/ml_devices/imageCache.cpp902
-rw-r--r--Src/Plugins/Library/ml_devices/imageCache.h76
-rw-r--r--Src/Plugins/Library/ml_devices/infoWidget.cpp399
-rw-r--r--Src/Plugins/Library/ml_devices/infoWidget.h27
-rw-r--r--Src/Plugins/Library/ml_devices/listWidget.cpp4096
-rw-r--r--Src/Plugins/Library/ml_devices/listWidget.h23
-rw-r--r--Src/Plugins/Library/ml_devices/listWidgetCategory.cpp202
-rw-r--r--Src/Plugins/Library/ml_devices/listWidgetCommand.cpp392
-rw-r--r--Src/Plugins/Library/ml_devices/listWidgetConnection.cpp262
-rw-r--r--Src/Plugins/Library/ml_devices/listWidgetGroup.cpp135
-rw-r--r--Src/Plugins/Library/ml_devices/listWidgetInternal.h1020
-rw-r--r--Src/Plugins/Library/ml_devices/listWidgetItem.cpp2854
-rw-r--r--Src/Plugins/Library/ml_devices/listWidgetPaint.cpp725
-rw-r--r--Src/Plugins/Library/ml_devices/listWidgetTooltip.cpp592
-rw-r--r--Src/Plugins/Library/ml_devices/local_menu.cpp183
-rw-r--r--Src/Plugins/Library/ml_devices/local_menu.h48
-rw-r--r--Src/Plugins/Library/ml_devices/main.cpp5
-rw-r--r--Src/Plugins/Library/ml_devices/main.h50
-rw-r--r--Src/Plugins/Library/ml_devices/managerView.cpp994
-rw-r--r--Src/Plugins/Library/ml_devices/managerView.h22
-rw-r--r--Src/Plugins/Library/ml_devices/ml_devices.rc172
-rw-r--r--Src/Plugins/Library/ml_devices/ml_devices.sln51
-rw-r--r--Src/Plugins/Library/ml_devices/ml_devices.vcxproj412
-rw-r--r--Src/Plugins/Library/ml_devices/ml_devices.vcxproj.filters310
-rw-r--r--Src/Plugins/Library/ml_devices/navigation.cpp1073
-rw-r--r--Src/Plugins/Library/ml_devices/navigation.h29
-rw-r--r--Src/Plugins/Library/ml_devices/navigationIcons.cpp343
-rw-r--r--Src/Plugins/Library/ml_devices/navigationIcons.h21
-rw-r--r--Src/Plugins/Library/ml_devices/plugin.cpp357
-rw-r--r--Src/Plugins/Library/ml_devices/plugin.h33
-rw-r--r--Src/Plugins/Library/ml_devices/png.rc50
-rw-r--r--Src/Plugins/Library/ml_devices/resource.h105
-rw-r--r--Src/Plugins/Library/ml_devices/resources/action-bg.pngbin0 -> 163 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/arrows.pngbin0 -> 365 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/attach-command-large.pngbin0 -> 788 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/attach-command-small.pngbin0 -> 239 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/cancel-sync-command-small.pngbin0 -> 297 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/command-bg.pngbin0 -> 242 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/command-secondary-bg.pngbin0 -> 163 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/commands/detach.pngbin0 -> 690 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/commands/eject.pngbin0 -> 1885 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/commands/settings.pngbin0 -> 2042 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/commands/sync.pngbin0 -> 819 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/connections/bluetooth.pngbin0 -> 340 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/connections/cloud.pngbin0 -> 551 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/connections/usb.pngbin0 -> 329 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/connections/wifi.pngbin0 -> 414 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/detach-command-large.pngbin0 -> 840 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/detach-command-small.pngbin0 -> 241 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/devices-title-en.pngbin0 -> 7572 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/devices.pngbin0 -> 64224 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/eject-command-small.pngbin0 -> 170 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/food/apple.pngbin0 -> 34151 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/food/banana.pngbin0 -> 138587 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/food/bread.pngbin0 -> 5914 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/food/broccoli.pngbin0 -> 52734 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/food/carrot.pngbin0 -> 52729 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/food/cereal.pngbin0 -> 150983 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/food/cucumber.pngbin0 -> 74812 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/food/eggplant.pngbin0 -> 84567 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/food/grape.pngbin0 -> 63881 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/food/lemon.pngbin0 -> 57675 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/food/muffin.pngbin0 -> 401367 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/food/orange.pngbin0 -> 58604 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/food/pasta.pngbin0 -> 126179 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/food/peach.pngbin0 -> 28831 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/food/pear.pngbin0 -> 66710 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/food/pepper.pngbin0 -> 202645 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/food/pretzel.pngbin0 -> 182594 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/food/rice.pngbin0 -> 237923 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/generic-device-160x160.pngbin0 -> 2491 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/generic-device-16x16.pngbin0 -> 210 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/item-hover.pngbin0 -> 206 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/item-select.pngbin0 -> 313 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/progress-large.pngbin0 -> 22461 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/progress-small.pngbin0 -> 844 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/spacebar.pngbin0 -> 537 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/sync-command-large.pngbin0 -> 819 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/sync-command-small.pngbin0 -> 268 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/unknown-command-large.pngbin0 -> 324 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/zoom/192x256.pngbin0 -> 5366 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/zoom/24x32.pngbin0 -> 347 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/zoom/48x64.pngbin0 -> 930 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/resources/zoom/96x128.pngbin0 -> 2518 bytes
-rw-r--r--Src/Plugins/Library/ml_devices/statusBar.cpp1001
-rw-r--r--Src/Plugins/Library/ml_devices/statusBar.h81
-rw-r--r--Src/Plugins/Library/ml_devices/strings.cpp195
-rw-r--r--Src/Plugins/Library/ml_devices/strings.h74
-rw-r--r--Src/Plugins/Library/ml_devices/version.rc239
-rw-r--r--Src/Plugins/Library/ml_devices/wasabi.cpp107
-rw-r--r--Src/Plugins/Library/ml_devices/wasabi.h43
-rw-r--r--Src/Plugins/Library/ml_devices/welcomeWidget.cpp760
-rw-r--r--Src/Plugins/Library/ml_devices/welcomeWidget.h20
-rw-r--r--Src/Plugins/Library/ml_devices/widget.cpp1284
-rw-r--r--Src/Plugins/Library/ml_devices/widget.h129
-rw-r--r--Src/Plugins/Library/ml_devices/widgetHost.cpp391
-rw-r--r--Src/Plugins/Library/ml_devices/widgetHost.h23
-rw-r--r--Src/Plugins/Library/ml_devices/widgetStyle.cpp261
-rw-r--r--Src/Plugins/Library/ml_devices/widgetStyle.h159
172 files changed, 33873 insertions, 0 deletions
diff --git a/Src/Plugins/Library/ml_devices/backBuffer.cpp b/Src/Plugins/Library/ml_devices/backBuffer.cpp
new file mode 100644
index 00000000..881c37bf
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/backBuffer.cpp
@@ -0,0 +1,276 @@
+#include "main.h"
+#include "./backBuffer.h"
+
+
+BOOL
+BackBuffer_Initialize(BackBuffer *self, HWND hwnd)
+{
+ if (NULL == self)
+ return FALSE;
+
+ ZeroMemory(self, sizeof(BackBuffer));
+
+ self->hwnd = hwnd;
+
+ return TRUE;
+}
+
+void
+BackBuffer_Uninitialize(BackBuffer *self)
+{
+ BackBuffer_Reset(self);
+}
+
+void
+BackBuffer_Reset(BackBuffer *self)
+{
+ if (NULL == self)
+ return;
+
+ if (NULL != self->hdc)
+ {
+ if (NULL != self->previous)
+ SelectBitmap(self->hdc, self->previous);
+
+ DeleteDC(self->hdc);
+ }
+
+ if (NULL != self->bitmap)
+ {
+ DeleteObject(self->bitmap);
+ }
+
+ ZeroMemory(self, sizeof(BackBuffer));
+}
+
+BOOL
+BackBuffer_EnsureSize(BackBuffer *self, long width, long height)
+{
+ return BackBuffer_EnsureSizeEx(self, width, height, width, height);
+}
+
+BOOL
+BackBuffer_EnsureSizeEx(BackBuffer *self, long width, long height, long allocWidth, long allocHeight)
+{
+ BOOL result;
+ HDC windowDC;
+ HBITMAP bitmap;
+ long bitmapWidth, bitmapHeight;
+
+ if (NULL == self)
+ return FALSE;
+
+ if (width < 0)
+ width = 0;
+
+ if (height < 0)
+ height = 0;
+
+ if (NULL != self->bitmap)
+ {
+ BITMAP bitmapInfo;
+ if (sizeof(bitmapInfo) == GetObject(self->bitmap, sizeof(bitmapInfo), &bitmapInfo))
+ {
+ if (bitmapInfo.bmWidth >= width && bitmapInfo.bmHeight >= height)
+ return TRUE;
+
+ bitmapWidth = bitmapInfo.bmWidth;
+ bitmapHeight = bitmapInfo.bmHeight;
+ }
+ else
+ {
+ bitmapWidth = 0;
+ bitmapHeight = 0;
+ }
+ }
+ else
+ {
+ bitmapWidth = 0;
+ bitmapHeight = 0;
+ }
+
+ result = FALSE;
+ bitmap = NULL;
+
+ windowDC = GetDCEx(self->hwnd, NULL, DCX_CACHE | DCX_NORESETATTRS);
+ if(NULL != windowDC)
+ {
+ if (allocWidth < width)
+ allocWidth = width;
+
+ if (allocWidth < bitmapWidth)
+ allocWidth = bitmapWidth;
+
+ if (allocHeight < height)
+ allocHeight = height;
+
+ if (allocHeight < bitmapHeight)
+ allocHeight = bitmapHeight;
+
+ bitmap = CreateCompatibleBitmap(windowDC, allocWidth, allocHeight);
+ ReleaseDC(self->hwnd, windowDC);
+ }
+
+ if (NULL != bitmap)
+ {
+ if (NULL != self->hdc)
+ SelectBitmap(self->hdc, bitmap);
+
+ if (NULL != self->bitmap)
+ DeleteObject(self->bitmap);
+
+ self->bitmap = bitmap;
+ result = TRUE;
+ }
+
+ return result;
+}
+
+HDC
+BackBuffer_GetDC(BackBuffer *self)
+{
+ if (NULL == self)
+ return FALSE;
+
+ if (NULL == self->hdc)
+ {
+ HDC windowDC;
+ windowDC = GetDCEx(self->hwnd, NULL, DCX_CACHE | DCX_NORESETATTRS);
+ if (NULL != windowDC)
+ {
+ self->hdc = CreateCompatibleDC(windowDC);
+ ReleaseDC(self->hwnd, windowDC);
+
+ if (NULL != self->hdc)
+ {
+ if (NULL != self->bitmap)
+ self->previous = SelectBitmap(self->hdc, self->bitmap);
+ else
+ self->previous = GetCurrentBitmap(self->hdc);
+ }
+ }
+ }
+
+ return self->hdc;
+}
+
+BOOL
+BackBuffer_Copy(BackBuffer *self, HDC hdc, long x, long y, long width, long height)
+{
+ HDC sourceDC;
+
+ if (NULL == self || NULL == self->bitmap)
+ return FALSE;
+
+ sourceDC = BackBuffer_GetDC(self);
+ if (NULL == sourceDC)
+ return FALSE;
+
+ return BitBlt(hdc, x, y, width, height, sourceDC, 0, 0, SRCCOPY);
+}
+
+BOOL
+BackBuffer_DrawTextEx(BackBuffer *self, HDC hdc, const wchar_t *string,
+ int length, RECT *rect, unsigned int format,
+ HFONT font, COLORREF backColor, COLORREF textColor, int backMode)
+{
+ BOOL result = FALSE;
+ RECT bufferRect;
+
+ if (NULL == hdc || NULL == rect)
+ return FALSE;
+
+ SetRect(&bufferRect, 0, 0, RECTWIDTH(*rect), RECTHEIGHT(*rect));
+
+ if (NULL != self &&
+ FALSE != BackBuffer_EnsureSize(self, bufferRect.right, bufferRect.bottom))
+ {
+ HDC bufferDC = BackBuffer_GetDC(self);
+ if (NULL != bufferDC)
+ {
+ HFONT prevFont;
+ prevFont = SelectFont(bufferDC, font);
+ SetTextColor(bufferDC, textColor);
+ SetBkColor(bufferDC, backColor);
+ SetBkMode(bufferDC, backMode);
+
+ if (OPAQUE == backMode)
+ ExtTextOut(bufferDC, 0, 0, ETO_OPAQUE, &bufferRect, NULL, 0, NULL);
+
+ if (FALSE != DrawText(bufferDC, string, length, &bufferRect, format))
+ {
+ result = BackBuffer_Copy(self, hdc, rect->left, rect->top,
+ bufferRect.right, bufferRect.bottom);
+ }
+
+ SelectFont(bufferDC, prevFont);
+ }
+ }
+
+
+ if (FALSE == result)
+ {
+ HFONT prevFont;
+ COLORREF prevBackColor, prevTextColor;
+ int prevBkMode;
+
+ prevFont = SelectFont(hdc, font);
+ prevTextColor = SetTextColor(hdc, textColor);
+ prevBackColor = SetBkColor(hdc, backColor);
+ prevBkMode= SetBkMode(hdc, backMode);
+
+ result = DrawText(hdc, string, length, rect, format);
+
+ SelectFont(hdc, prevFont);
+ SetTextColor(hdc, prevTextColor);
+ SetBkColor(hdc, prevBackColor);
+ SetBkMode(hdc, prevBkMode);
+ }
+
+ return result;
+}
+
+BOOL
+BackBuffer_DrawText(BackBuffer *self, HDC hdc, const wchar_t *string,
+ int length, RECT *rect, unsigned int format)
+{
+ BOOL result = FALSE;
+ RECT bufferRect;
+
+ if (NULL == hdc || NULL == rect)
+ return FALSE;
+
+ SetRect(&bufferRect, 0, 0, RECTWIDTH(*rect), RECTHEIGHT(*rect));
+
+ if (NULL != self &&
+ FALSE != BackBuffer_EnsureSize(self, bufferRect.right, bufferRect.bottom))
+ {
+ HDC bufferDC = BackBuffer_GetDC(self);
+ if (NULL != bufferDC)
+ {
+ HFONT prevFont;
+ int backMode;
+ prevFont = SelectFont(bufferDC, GetCurrentFont(hdc));
+ SetTextColor(bufferDC, GetTextColor(hdc));
+ SetBkColor(bufferDC, GetBkColor(hdc));
+ backMode = GetBkMode(hdc);
+ SetBkMode(bufferDC, backMode);
+
+ if (OPAQUE == backMode)
+ ExtTextOut(bufferDC, 0, 0, ETO_OPAQUE, &bufferRect, NULL, 0, NULL);
+
+ if (FALSE != DrawText(bufferDC, string, length, &bufferRect, format))
+ {
+ result = BackBuffer_Copy(self, hdc, rect->left, rect->top,
+ bufferRect.right, bufferRect.bottom);
+ }
+
+ SelectFont(bufferDC, prevFont);
+ }
+ }
+
+ if (FALSE == result)
+ result = DrawText(hdc, string, length, rect, format);
+
+ return result;
+}
diff --git a/Src/Plugins/Library/ml_devices/backBuffer.h b/Src/Plugins/Library/ml_devices/backBuffer.h
new file mode 100644
index 00000000..6a0031f8
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/backBuffer.h
@@ -0,0 +1,64 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_BACK_BUFFER_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_BACK_BUFFER_HEADER
+
+typedef struct BackBuffer
+{
+ HBITMAP bitmap;
+ HWND hwnd;
+ HDC hdc;
+ HBITMAP previous;
+} BackBuffer;
+
+BOOL
+BackBuffer_Initialize(BackBuffer *self, HWND hwnd);
+
+void
+BackBuffer_Uninitialize(BackBuffer *self);
+
+BOOL
+BackBuffer_EnsureSize(BackBuffer *self,
+ long width,
+ long height);
+
+BOOL
+BackBuffer_EnsureSizeEx(BackBuffer *self,
+ long width,
+ long height,
+ long allocWidth,
+ long allocHeight);
+
+HDC
+BackBuffer_GetDC(BackBuffer *self);
+
+BOOL
+BackBuffer_Copy(BackBuffer *self,
+ HDC hdc,
+ long x,
+ long y,
+ long width,
+ long height);
+
+void
+BackBuffer_Reset(BackBuffer *self);
+
+BOOL
+BackBuffer_DrawText(BackBuffer *self,
+ HDC hdc,
+ const wchar_t *string,
+ int length,
+ RECT *rect,
+ unsigned int format);
+
+BOOL
+BackBuffer_DrawTextEx(BackBuffer *self,
+ HDC hdc,
+ const wchar_t *string,
+ int length,
+ RECT *rect,
+ unsigned int format,
+ HFONT font,
+ COLORREF backColor,
+ COLORREF textColor,
+ int backMode);
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_BACK_BUFFER_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/common.h b/Src/Plugins/Library/ml_devices/common.h
new file mode 100644
index 00000000..8713a2bd
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/common.h
@@ -0,0 +1,76 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_COMMON_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_COMMON_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#ifndef LONGX86
+#ifdef _WIN64
+ #define LONGX86 LONG_PTR
+#else /*_WIN64*/
+ #define LONGX86 LONG
+#endif /*_WIN64*/
+#endif // LONGX86
+
+#define CSTR_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
+
+#ifdef __cplusplus
+ #define SENDMSG(__hwnd, __msgId, __wParam, __lParam) ::SendMessageW((__hwnd), (__msgId), (__wParam), (__lParam))
+#else
+ #define SENDMSG(__hwnd, __msgId, __wParam, __lParam) SendMessageW((__hwnd), (__msgId), (__wParam), (__lParam))
+#endif // __cplusplus
+
+#define SENDMLIPC(__hwndML, __ipcMsgId, __param) SENDMSG((__hwndML), WM_ML_IPC, (WPARAM)(__param), (LPARAM)(__ipcMsgId))
+
+#define SENDWAIPC(__hwndWA, __ipcMsgId, __param) SENDMSG((__hwndWA), WM_WA_IPC, (WPARAM)(__param), (LPARAM)(__ipcMsgId))
+
+#define SENDCMD(__hwnd, __ctrlId, __eventId, __hctrl) (SENDMSG((__hwnd), WM_COMMAND, MAKEWPARAM(__ctrlId, __eventId), (LPARAM)(__hctrl)))
+
+#define DIALOG_RESULT(__hwnd, __result) { SetWindowLongPtrW((__hwnd), DWLP_MSGRESULT, ((LONGX86)(LONG_PTR)(__result))); return TRUE; }
+
+#ifndef GetWindowStyle
+ #define GetWindowStyle(__hwnd) ((UINT)GetWindowLongPtr((__hwnd), GWL_STYLE))
+#endif //GetWindowStyle
+
+#ifndef SetWindowStyle
+ #define SetWindowStyle(__hwnd, __style) (SetWindowLongPtr((__hwnd), GWL_STYLE, (__style)))
+#endif //SetWindowStyle
+
+#ifndef GetWindowStyleEx
+ #define GetWindowStyleEx(__hwnd) ((UINT)GetWindowLongPtr((__hwnd), GWL_EXSTYLE))
+#endif // GetWindowStyleEx
+
+#ifndef SetWindowStyleEx
+ #define SetWindowStyleEx(__hwnd, __style) (SetWindowLongPtr((__hwnd), GWL_EXSTYLE, (__style)))
+#endif //SetWindowStyle
+
+#ifndef RECTWIDTH
+ #define RECTWIDTH(__r) ((__r).right - (__r).left)
+#endif
+
+#ifndef RECTHEIGHT
+ #define RECTHEIGHT(__r) ((__r).bottom - (__r).top)
+#endif
+
+#undef CLAMP
+#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
+
+#ifndef ARRAYSIZE
+ #define ARRAYSIZE(_a) (sizeof(_a)/sizeof((_a)[0]))
+#endif
+
+#ifndef ABS
+ #define ABS(x) (((x) > 0) ? (x) : (-x))
+#endif
+
+#ifndef MIN
+ #define MIN(v1, v2) (((v1) < (v2)) ? (v1) : (v2))
+#endif
+
+#ifndef MAX
+ #define MAX(v1, v2) (((v1) > (v2)) ? (v1) : (v2))
+#endif
+
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_COMMON_HEADER
diff --git a/Src/Plugins/Library/ml_devices/config.cpp b/Src/Plugins/Library/ml_devices/config.cpp
new file mode 100644
index 00000000..e2abd6bb
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/config.cpp
@@ -0,0 +1,119 @@
+#include "main.h"
+#include "./config.h"
+
+#include <strsafe.h>
+
+#define CONFIG_SUFFIX L"Plugins\\ml"
+#define CONFIG_FILE L"ml_devices.ini"
+
+static const char *
+Config_GetPath(BOOL ensureExist)
+{
+ static const char *configPath = NULL;
+ if (NULL == configPath)
+ {
+ const wchar_t *userPath;
+ wchar_t buffer[MAX_PATH * 2] = {0};
+
+ if (NULL == WASABI_API_APP)
+ return NULL;
+
+ userPath = WASABI_API_APP->path_getUserSettingsPath();
+ if (NULL == userPath)
+ return NULL;
+
+ if (0 != PathCombine(buffer, userPath, CONFIG_SUFFIX))
+ {
+ if ((FALSE == ensureExist || SUCCEEDED(Plugin_EnsurePathExist(buffer))) &&
+ FALSE != PathAppend(buffer,CONFIG_FILE))
+ {
+ configPath = String_ToAnsi(CP_UTF8, 0, buffer, -1, NULL, NULL);
+ }
+ }
+ }
+
+ return configPath;
+}
+
+
+unsigned long
+Config_ReadString(const char *section, const char *key, const char *defaultValue, char *returnedString, unsigned long size)
+{
+ return GetPrivateProfileStringA(section, key, defaultValue, returnedString, size, Config_GetPath(FALSE));
+}
+
+unsigned int
+Config_ReadInt(const char *section, const char *key, int defaultValue)
+{
+ return GetPrivateProfileIntA(section, key, defaultValue, Config_GetPath(FALSE));
+}
+
+BOOL
+Config_ReadBool(const char *section, const char *key, BOOL defaultValue)
+{
+ char buffer[32] = {0};
+ int length = Config_ReadString(section, key, NULL, buffer, ARRAYSIZE(buffer));
+
+ if (0 == length)
+ return defaultValue;
+
+ if (1 == length)
+ {
+ switch(*buffer)
+ {
+ case '0':
+ case 'n':
+ case 'f':
+ return FALSE;
+ case '1':
+ case 'y':
+ case 't':
+ return TRUE;
+ }
+ }
+ else
+ {
+ if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, NORM_IGNORECASE, "yes", -1, buffer, length) ||
+ CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, NORM_IGNORECASE, "true", -1, buffer, length))
+ {
+ return TRUE;
+ }
+
+ if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, NORM_IGNORECASE, "no", -1, buffer, length) ||
+ CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, NORM_IGNORECASE, "false", -1, buffer, length))
+ {
+ return FALSE;
+ }
+ }
+
+ if (FALSE != StrToIntExA(buffer, STIF_SUPPORT_HEX, &length))
+ return (0 != length);
+
+ return defaultValue;
+}
+
+BOOL
+Config_WriteString(const char *section, const char *key, const char *value)
+{
+ const char *configPath = Config_GetPath(TRUE);
+ if (NULL == configPath || '\0' == *configPath)
+ return FALSE;
+
+ return (0 != WritePrivateProfileStringA(section, key, value, configPath));
+}
+
+BOOL
+Config_WriteInt(const char *section, const char *key, int value)
+{
+ char buffer[32] = {0};
+ if (FAILED(StringCchPrintfA(buffer, ARRAYSIZE(buffer), "%d", value)))
+ return FALSE;
+
+ return Config_WriteString(section, key, buffer);
+}
+
+BOOL
+Config_WriteBool(const char *section, const char *key, BOOL value)
+{
+ return Config_WriteString(section, key, (FALSE != value) ? "yes" : "no");
+}
diff --git a/Src/Plugins/Library/ml_devices/config.h b/Src/Plugins/Library/ml_devices/config.h
new file mode 100644
index 00000000..a2b17978
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/config.h
@@ -0,0 +1,44 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_CONFIG_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_CONFIG_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+
+unsigned long
+Config_ReadString(const char *section,
+ const char *key,
+ const char *defaultValue,
+ char *returnedStrign,
+ unsigned long size);
+
+unsigned int
+Config_ReadInt(const char *section,
+ const char *key,
+ int defaultValue);
+
+BOOL
+Config_ReadBool(const char *section,
+ const char *key,
+ BOOL defaultValue);
+
+BOOL
+Config_WriteString(const char *section,
+ const char *key,
+ const char *value);
+
+
+BOOL
+Config_WriteInt(const char *section,
+ const char *key,
+ int value);
+
+BOOL
+Config_WriteBool(const char *section,
+ const char *key,
+ BOOL value);
+
+#endif // _NULLSOFT_WINAMP_ML_DEVICES_CONFIG_HEADER
diff --git a/Src/Plugins/Library/ml_devices/deviceCommands.cpp b/Src/Plugins/Library/ml_devices/deviceCommands.cpp
new file mode 100644
index 00000000..2f3ba8e4
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/deviceCommands.cpp
@@ -0,0 +1,207 @@
+#include "main.h"
+#include "deviceCommands.h"
+
+
+typedef struct DeviceCommandInfo
+{
+ const char *name;
+ unsigned int title;
+ unsigned int description;
+ unsigned int smallIcon;
+ unsigned int largeIcon;
+} DeviceCommandInfo;
+
+
+static DeviceCommandInfo registeredCommands[] =
+{
+ { "sync",
+ IDS_SYNC_COMMAND_TITLE,
+ IDS_SYNC_COMMAND_DESC,
+ IDR_SYNC_COMMAND_SMALL_IMAGE,
+ IDR_SYNC_COMMAND_LARGE_IMAGE },
+ { "cancel_sync",
+ IDS_CANCEL_SYNC_COMMAND_TITLE,
+ IDS_CANCEL_SYNC_COMMAND_DESC,
+ IDR_CANCEL_SYNC_COMMAND_SMALL_IMAGE,
+ IDR_CANCEL_SYNC_COMMAND_LARGE_IMAGE },
+ { "attach",
+ IDS_ATTACH_COMMAND_TITLE,
+ IDS_ATTACH_COMMAND_DESC,
+ IDR_ATTACH_COMMAND_SMALL_IMAGE,
+ IDR_ATTACH_COMMAND_LARGE_IMAGE },
+ { "detach",
+ IDS_DETACH_COMMAND_TITLE,
+ IDS_DETACH_COMMAND_DESC,
+ IDR_DETACH_COMMAND_SMALL_IMAGE,
+ IDR_DETACH_COMMAND_LARGE_IMAGE },
+ { "eject",
+ IDS_EJECT_COMMAND_TITLE,
+ IDS_EJECT_COMMAND_DESC,
+ IDR_EJECT_COMMAND_SMALL_IMAGE,
+ IDR_EJECT_COMMAND_LARGE_IMAGE },
+ { "rename",
+ IDS_RENAME_COMMAND_TITLE,
+ IDS_RENAME_COMMAND_DESC,
+ IDR_RENAME_COMMAND_SMALL_IMAGE,
+ IDR_RENAME_COMMAND_LARGE_IMAGE },
+ { "view_open",
+ IDS_VIEW_OPEN_COMMAND_TITLE,
+ IDS_VIEW_OPEN_COMMAND_DESC,
+ IDR_VIEW_OPEN_COMMAND_SMALL_IMAGE,
+ IDR_VIEW_OPEN_COMMAND_LARGE_IMAGE },
+ { "preferences",
+ IDS_PREFERENCES_COMMAND_TITLE,
+ IDS_PREFERENCES_COMMAND_DESC,
+ IDR_PREFERENCES_COMMAND_SMALL_IMAGE,
+ IDR_PREFERENCES_COMMAND_LARGE_IMAGE },
+ { "playlist_create",
+ IDS_PLAYLIST_CREATE_COMMAND_TITLE,
+ IDS_PLAYLIST_CREATE_COMMAND_DESC,
+ IDR_PLAYLIST_CREATE_COMMAND_SMALL_IMAGE,
+ IDR_PLAYLIST_CREATE_COMMAND_LARGE_IMAGE },
+};
+
+static ifc_devicecommand * _cdecl
+DeviceCommands_RegisterCommandCb(const char *name, void *user)
+{
+ size_t index;
+ wchar_t buffer[2048] = {0};
+ DeviceCommandInfo *commandInfo;
+ ifc_devicecommand *command;
+ ifc_devicecommandeditor *editor;
+
+ commandInfo = NULL;
+ for(index = 0; index < ARRAYSIZE(registeredCommands); index++)
+ {
+ if (name == registeredCommands[index].name)
+ {
+ commandInfo = &registeredCommands[index];
+ break;
+ }
+ }
+
+ if (NULL == commandInfo)
+ return NULL;
+
+ if (NULL == WASABI_API_DEVICES)
+ return NULL;
+
+ if (FAILED(WASABI_API_DEVICES->CreateCommand(commandInfo->name, &command)))
+ return NULL;
+
+ if (FAILED(command->QueryInterface(IFC_DeviceCommandEditor, (void**)&editor)))
+ {
+ command->Release();
+ return NULL;
+ }
+
+ if (0 != commandInfo->title)
+ {
+ WASABI_API_LNGSTRINGW_BUF(commandInfo->title, buffer, ARRAYSIZE(buffer));
+ editor->SetDisplayName(buffer);
+ }
+
+ if (0 != commandInfo->description)
+ {
+ WASABI_API_LNGSTRINGW_BUF(commandInfo->description, buffer, ARRAYSIZE(buffer));
+ editor->SetDescription(buffer);
+ }
+
+ if (0 != commandInfo->smallIcon || 0 != commandInfo->largeIcon)
+ {
+ ifc_deviceiconstore *iconStore;
+ if (SUCCEEDED(editor->GetIconStore(&iconStore)))
+ {
+ if (0 != commandInfo->smallIcon)
+ {
+ if (FALSE != Plugin_GetResourceString(MAKEINTRESOURCE(commandInfo->smallIcon), RT_RCDATA, buffer, ARRAYSIZE(buffer)))
+ iconStore->Add(buffer, 16, 16, TRUE);
+ }
+
+ if (0 != commandInfo->largeIcon)
+ {
+ if (FALSE != Plugin_GetResourceString(MAKEINTRESOURCE(commandInfo->largeIcon), RT_RCDATA, buffer, ARRAYSIZE(buffer)))
+ iconStore->Add(buffer, 43, 24, TRUE);
+ }
+
+ iconStore->Release();
+ }
+ }
+
+ editor->Release();
+ return command;
+}
+
+BOOL
+DeviceCommands_Register()
+{
+ const char *commands[ARRAYSIZE(registeredCommands)];
+ size_t index;
+
+ if (NULL == WASABI_API_DEVICES)
+ return FALSE;
+
+ for(index = 0; index < ARRAYSIZE(commands); index++)
+ {
+ commands[index] = registeredCommands[index].name;
+ }
+
+ WASABI_API_DEVICES->CommandRegisterIndirect(commands, ARRAYSIZE(commands), DeviceCommands_RegisterCommandCb, NULL);
+
+ return TRUE;
+}
+
+
+BOOL
+DeviceCommand_GetSupported(ifc_device *device, const char *name, DeviceCommandContext context,
+ ifc_devicesupportedcommand **commandOut)
+{
+ ifc_devicesupportedcommandenum *enumerator;
+ ifc_devicesupportedcommand *command;
+ BOOL foundCommand;
+
+ if (NULL == device ||
+ NULL == name ||
+ FAILED(device->EnumerateCommands(&enumerator, context)))
+ {
+ return FALSE;
+ }
+
+ foundCommand = FALSE;
+
+ while (S_OK == enumerator->Next(&command, 1, NULL))
+ {
+ if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, 0, name, -1, command->GetName(), -1))
+ {
+ foundCommand = TRUE;
+
+ if (NULL != commandOut)
+ *commandOut = command;
+ else
+ command->Release();
+
+ break;
+ }
+ command->Release();
+ }
+
+ enumerator->Release();
+ return foundCommand;
+}
+
+BOOL
+DeviceCommand_GetEnabled(ifc_device *device, const char *name, DeviceCommandContext context)
+{
+ ifc_devicesupportedcommand *command;
+ DeviceCommandFlags flags;
+
+ if (FALSE == DeviceCommand_GetSupported(device, name, context, &command))
+ return FALSE;
+
+ if (FAILED(command->GetFlags(&flags)))
+ flags = DeviceCommandFlag_Disabled;
+
+ command->Release();
+
+ return (0 == (DeviceCommandFlag_Disabled & flags));
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/deviceCommands.h b/Src/Plugins/Library/ml_devices/deviceCommands.h
new file mode 100644
index 00000000..bd021364
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/deviceCommands.h
@@ -0,0 +1,24 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_DEVICE_COMMANDS_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_DEVICE_COMMANDS_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+BOOL
+DeviceCommands_Register();
+
+BOOL
+DeviceCommand_GetSupported(ifc_device *device,
+ const char *name,
+ DeviceCommandContext context,
+ ifc_devicesupportedcommand **commandOut);
+
+BOOL
+DeviceCommand_GetEnabled(ifc_device *device,
+ const char *name,
+ DeviceCommandContext context);
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_DEVICE_COMMANDS_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/deviceHandler.cpp b/Src/Plugins/Library/ml_devices/deviceHandler.cpp
new file mode 100644
index 00000000..7140f662
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/deviceHandler.cpp
@@ -0,0 +1,206 @@
+#include "main.h"
+#include "./deviceHandler.h"
+
+DeviceHandler::DeviceHandler()
+ : ref(1), relayWindow(NULL)
+{
+}
+
+DeviceHandler::~DeviceHandler()
+{
+}
+
+HRESULT DeviceHandler::CreateInstance(DeviceHandler **instance)
+{
+ if (NULL == instance)
+ return E_POINTER;
+
+ *instance = new DeviceHandler();
+
+ if (NULL == *instance)
+ return E_OUTOFMEMORY;
+
+ return S_OK;
+}
+
+size_t DeviceHandler::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+size_t DeviceHandler::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+int DeviceHandler::QueryInterface(GUID interface_guid, void **object)
+{
+ if (NULL == object) return E_POINTER;
+
+ if (IsEqualIID(interface_guid, IFC_DeviceEvent))
+ *object = static_cast<ifc_deviceevent*>(this);
+ else
+ {
+ *object = NULL;
+ return E_NOINTERFACE;
+ }
+
+ if (NULL == *object)
+ return E_UNEXPECTED;
+
+ AddRef();
+ return S_OK;
+}
+
+
+void DeviceHandler::IconChanged(ifc_device *device)
+{
+ if (NULL != relayWindow)
+ EVENTRELAY_NOTIFY_DEVICE(relayWindow, device, Event_DeviceIconChanged);
+}
+
+void DeviceHandler::DisplayNameChanged(ifc_device *device, const wchar_t *displayName)
+{
+ if (NULL != relayWindow)
+ EVENTRELAY_NOTIFY_DEVICE(relayWindow, device, Event_DeviceDisplayNameChanged);
+}
+
+void DeviceHandler::AttachmentChanged(ifc_device *device, BOOL attached)
+{
+ if (NULL != relayWindow)
+ {
+ if (FALSE != attached)
+ {
+ EVENTRELAY_NOTIFY_DEVICE(relayWindow, device, Event_DeviceAttached);
+ }
+ else
+ {
+ EVENTRELAY_NOTIFY_DEVICE(relayWindow, device, Event_DeviceDetached);
+ }
+ }
+}
+
+void DeviceHandler::VisibilityChanged(ifc_device *device, BOOL visible)
+{
+ if (NULL != relayWindow)
+ {
+ if (FALSE == visible)
+ {
+ EVENTRELAY_NOTIFY_DEVICE(relayWindow, device, Event_DeviceHidden);
+ }
+ else
+ {
+ EVENTRELAY_NOTIFY_DEVICE(relayWindow, device, Event_DeviceShown);
+ }
+ }
+}
+
+void DeviceHandler::TotalSpaceChanged(ifc_device *device, size_t space)
+{
+ if (NULL != relayWindow)
+ EVENTRELAY_NOTIFY_DEVICE(relayWindow, device, Event_DeviceTotalSpaceChanged);
+}
+
+void DeviceHandler::UsedSpaceChanged(ifc_device *device, size_t space)
+{
+ if (NULL != relayWindow)
+ EVENTRELAY_NOTIFY_DEVICE(relayWindow, device, Event_DeviceUsedSpaceChanged);
+}
+
+void DeviceHandler::CommandChanged(ifc_device *device)
+{
+ if (NULL != relayWindow)
+ EVENTRELAY_NOTIFY_DEVICE(relayWindow, device, Event_DeviceCommandChanged);
+}
+
+
+void DeviceHandler::ActivityStarted(ifc_device *device, ifc_deviceactivity *activity)
+{
+ if (NULL != relayWindow)
+ EVENTRELAY_NOTIFY_DEVICE(relayWindow, device, Event_DeviceActivityStarted);
+}
+
+void DeviceHandler::ActivityFinished(ifc_device *device, ifc_deviceactivity *activity)
+{
+ if (NULL != relayWindow)
+ EVENTRELAY_NOTIFY_DEVICE(relayWindow, device, Event_DeviceActivityFinished);
+}
+
+void DeviceHandler::ActivityChanged(ifc_device *device, ifc_deviceactivity *activity)
+{
+ if (NULL != relayWindow)
+ EVENTRELAY_NOTIFY_DEVICE(relayWindow, device, Event_DeviceActivityChanged);
+}
+
+void DeviceHandler::ModelChanged(ifc_device *device, const wchar_t *model)
+{
+ if (NULL != relayWindow)
+ EVENTRELAY_NOTIFY_DEVICE(relayWindow, device, Event_DeviceModelChanged);
+}
+
+void DeviceHandler::StatusChanged(ifc_device *device, const wchar_t *status)
+{
+ if (NULL != relayWindow)
+ EVENTRELAY_NOTIFY_DEVICE(relayWindow, device, Event_DeviceStatusChanged);
+}
+
+HRESULT DeviceHandler::SetRelayWindow(HWND hwnd)
+{
+ relayWindow = hwnd;
+ return S_OK;
+}
+
+HRESULT DeviceHandler::Advise(ifc_device *device)
+{
+ HRESULT hr;
+
+ if (NULL == device)
+ return E_INVALIDARG;
+
+ hr = device->Advise(this);
+ if (FAILED(hr))
+ return hr;
+
+ return hr;
+}
+
+HRESULT DeviceHandler::Unadvise(ifc_device *device)
+{
+ HRESULT hr;
+
+ if (NULL == device)
+ return E_INVALIDARG;
+
+ hr = device->Unadvise(this);
+ if (FAILED(hr))
+ return hr;
+
+ return hr;
+}
+
+#define CBCLASS DeviceHandler
+START_DISPATCH;
+CB(ADDREF, AddRef)
+CB(RELEASE, Release)
+CB(QUERYINTERFACE, QueryInterface)
+VCB(API_ICONCHANGED, IconChanged)
+VCB(API_DISPLAYNAMECHANGED, DisplayNameChanged)
+VCB(API_ATTACHMENTCHANGED, AttachmentChanged)
+VCB(API_VISIBILITYCHANGED, VisibilityChanged)
+VCB(API_TOTALSPACECHANGED, TotalSpaceChanged)
+VCB(API_USEDSPACECHANGED, UsedSpaceChanged)
+VCB(API_COMMANDCHANGED, CommandChanged)
+VCB(API_ACTIVITYSTARTED, ActivityStarted)
+VCB(API_ACTIVITYFINISHED, ActivityFinished)
+VCB(API_ACTIVITYCHANGED, ActivityChanged)
+VCB(API_MODELCHANGED, ModelChanged)
+VCB(API_STATUSCHANGED, StatusChanged)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/deviceHandler.h b/Src/Plugins/Library/ml_devices/deviceHandler.h
new file mode 100644
index 00000000..67d6bc00
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/deviceHandler.h
@@ -0,0 +1,56 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_DEVICE_HANDLER_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_DEVICE_HANDLER_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+#include "../devices/ifc_deviceevent.h"
+
+class DeviceHandler: public ifc_deviceevent
+{
+protected:
+ DeviceHandler();
+ ~DeviceHandler();
+
+public:
+ static HRESULT CreateInstance(DeviceHandler **instance);
+
+public:
+ /* Dispatchable */
+ size_t AddRef();
+ size_t Release();
+ int QueryInterface(GUID interface_guid, void **object);
+
+ /* ifc_deviceevent */
+ void IconChanged(ifc_device *device);
+ void DisplayNameChanged(ifc_device *device, const wchar_t *displayName);
+ void AttachmentChanged(ifc_device *device, BOOL attached);
+ void VisibilityChanged(ifc_device *device, BOOL visible);
+ void TotalSpaceChanged(ifc_device *device, size_t space);
+ void UsedSpaceChanged(ifc_device *device, size_t space);
+ void CommandChanged(ifc_device *device);
+ void ActivityStarted(ifc_device *device, ifc_deviceactivity *activity);
+ void ActivityFinished(ifc_device *device, ifc_deviceactivity *activity);
+ void ActivityChanged(ifc_device *device, ifc_deviceactivity *activity);
+ void ModelChanged(ifc_device *device, const wchar_t *model);
+ void StatusChanged(ifc_device *device, const wchar_t *status);
+
+public:
+ HRESULT SetRelayWindow(HWND hwnd);
+ HRESULT Advise(ifc_device *device);
+ HRESULT Unadvise(ifc_device *device);
+
+
+protected:
+ size_t ref;
+ HWND relayWindow;
+
+protected:
+ RECVS_DISPATCH;
+};
+
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_DEVICE_HANDLER_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/deviceManagerHandler.cpp b/Src/Plugins/Library/ml_devices/deviceManagerHandler.cpp
new file mode 100644
index 00000000..7bcbfe5d
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/deviceManagerHandler.cpp
@@ -0,0 +1,174 @@
+#include "main.h"
+#include "./deviceManagerHandler.h"
+
+DeviceManagerHandler::DeviceManagerHandler()
+ : ref(1), relayWindow(NULL)
+{
+}
+
+DeviceManagerHandler::~DeviceManagerHandler()
+{
+}
+
+HRESULT DeviceManagerHandler::CreateInstance(DeviceManagerHandler **instance)
+{
+ if (NULL == instance)
+ return E_POINTER;
+
+ *instance = new DeviceManagerHandler();
+
+ if (NULL == *instance)
+ return E_OUTOFMEMORY;
+
+ return S_OK;
+}
+
+size_t DeviceManagerHandler::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+size_t DeviceManagerHandler::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+int DeviceManagerHandler::QueryInterface(GUID interface_guid, void **object)
+{
+ if (NULL == object) return E_POINTER;
+
+ if (IsEqualIID(interface_guid, IFC_DeviceManagerEvent))
+ *object = static_cast<ifc_devicemanagerevent*>(this);
+ else
+ {
+ *object = NULL;
+ return E_NOINTERFACE;
+ }
+
+ if (NULL == *object)
+ return E_UNEXPECTED;
+
+ AddRef();
+ return S_OK;
+}
+
+void DeviceManagerHandler::TypeAdded(api_devicemanager *manager, ifc_devicetype *type)
+{
+ if (NULL != relayWindow)
+ EVENTRELAY_NOTIFY_TYPE(relayWindow, type, Event_TypeRegistered);
+}
+
+void DeviceManagerHandler::TypeRemoved(api_devicemanager *manager, ifc_devicetype *type)
+{
+ if (NULL != relayWindow)
+ EVENTRELAY_NOTIFY_TYPE(relayWindow, type, Event_TypeUnregistered);
+}
+
+void DeviceManagerHandler::ConnectionAdded(api_devicemanager *manager, ifc_deviceconnection *connection)
+{
+ if (NULL != relayWindow)
+ EVENTRELAY_NOTIFY_CONNECTION(relayWindow, connection, Event_ConnectionRegistered);
+}
+
+void DeviceManagerHandler::ConnectionRemoved(api_devicemanager *manager, ifc_deviceconnection *connection)
+{
+ if (NULL != relayWindow)
+ EVENTRELAY_NOTIFY_CONNECTION(relayWindow, connection, Event_ConnectionUnregistered);
+}
+
+void DeviceManagerHandler::CommandAdded(api_devicemanager *manager, ifc_devicecommand *command)
+{
+ if (NULL != relayWindow)
+ EVENTRELAY_NOTIFY_COMMAND(relayWindow, command, Event_CommandRegistered);
+}
+
+void DeviceManagerHandler::CommandRemoved(api_devicemanager *manager, ifc_devicecommand *command)
+{
+ if (NULL != relayWindow)
+ EVENTRELAY_NOTIFY_COMMAND(relayWindow, command, Event_CommandUnregistered);
+}
+
+void DeviceManagerHandler::DeviceAdded(api_devicemanager *manager, ifc_device *device)
+{
+ if (NULL != relayWindow)
+ EVENTRELAY_NOTIFY_DEVICE(relayWindow, device, Event_DeviceAdded);
+}
+
+void DeviceManagerHandler::DeviceRemoved(api_devicemanager *manager, ifc_device *device)
+{
+ if (NULL != relayWindow)
+ EVENTRELAY_NOTIFY_DEVICE(relayWindow, device, Event_DeviceRemoved);
+}
+
+void DeviceManagerHandler::DiscoveryStarted(api_devicemanager *manager)
+{
+ if (NULL != relayWindow)
+ EVENTRELAY_NOTIFY_DISCOVERY(relayWindow, manager, Event_DiscoveryStarted);
+}
+
+void DeviceManagerHandler::DiscoveryFinished(api_devicemanager *manager)
+{
+ if (NULL != relayWindow)
+ EVENTRELAY_NOTIFY_DISCOVERY(relayWindow, manager, Event_DiscoveryFinished);
+}
+
+HRESULT DeviceManagerHandler::SetRelayWindow(HWND hwnd)
+{
+ relayWindow = hwnd;
+ return S_OK;
+}
+
+HRESULT DeviceManagerHandler::Advise(api_devicemanager *manager)
+{
+ HRESULT hr;
+
+ if (NULL == manager)
+ return E_INVALIDARG;
+
+ hr = manager->Advise(this);
+ if (FAILED(hr))
+ return hr;
+
+ return hr;
+}
+
+HRESULT DeviceManagerHandler::Unadvise(api_devicemanager *manager)
+{
+ HRESULT hr;
+
+ if (NULL == manager)
+ return E_INVALIDARG;
+
+ hr = manager->Unadvise(this);
+ if (FAILED(hr))
+ return hr;
+
+
+ return hr;
+}
+
+
+#define CBCLASS DeviceManagerHandler
+START_DISPATCH;
+CB(ADDREF, AddRef)
+CB(RELEASE, Release)
+CB(QUERYINTERFACE, QueryInterface)
+VCB(API_TYPEADDED, TypeAdded)
+VCB(API_TYPEREMOVED, TypeRemoved)
+VCB(API_CONNECTIONADDED, ConnectionAdded)
+VCB(API_CONNECTIONREMOVED, ConnectionRemoved)
+VCB(API_COMMANDADDED, CommandAdded)
+VCB(API_COMMANDREMOVED, CommandRemoved)
+VCB(API_DEVICEADDED, DeviceAdded)
+VCB(API_DEVICEREMOVED, DeviceRemoved)
+VCB(API_DISCOVERYSTARTED, DiscoveryStarted)
+VCB(API_DISCOVERYFINISHED, DiscoveryFinished)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/deviceManagerHandler.h b/Src/Plugins/Library/ml_devices/deviceManagerHandler.h
new file mode 100644
index 00000000..568627fa
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/deviceManagerHandler.h
@@ -0,0 +1,54 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_DEVICE_MANAGER_HANDLER_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_DEVICE_MANAGER_HANDLER_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../devices/api_devicemanager.h"
+//#include <ifc_devicemanagerevent.h>
+
+class DeviceManagerHandler: public ifc_devicemanagerevent
+{
+protected:
+ DeviceManagerHandler();
+ ~DeviceManagerHandler();
+
+public:
+ static HRESULT CreateInstance(DeviceManagerHandler **instance);
+
+public:
+ /* Dispatchable */
+ size_t AddRef();
+ size_t Release();
+ int QueryInterface(GUID interface_guid, void **object);
+
+ /* ifc_devicemanagerevent */
+ void TypeAdded(api_devicemanager *manager, ifc_devicetype *type);
+ void TypeRemoved(api_devicemanager *manager, ifc_devicetype *type);
+ void ConnectionAdded(api_devicemanager *manager, ifc_deviceconnection *connection);
+ void ConnectionRemoved(api_devicemanager *manager, ifc_deviceconnection *connection);
+ void CommandAdded(api_devicemanager *manager, ifc_devicecommand *command);
+ void CommandRemoved(api_devicemanager *manager, ifc_devicecommand *command);
+ void DeviceAdded(api_devicemanager *manager, ifc_device *device);
+ void DeviceRemoved(api_devicemanager *manager, ifc_device *device);
+ void DiscoveryStarted(api_devicemanager *manager);
+ void DiscoveryFinished(api_devicemanager *manager);
+
+public:
+ HRESULT SetRelayWindow(HWND hwnd);
+ HRESULT Advise(api_devicemanager *manager);
+ HRESULT Unadvise(api_devicemanager *manager);
+
+
+protected:
+ size_t ref;
+ HWND relayWindow;
+
+protected:
+ RECVS_DISPATCH;
+};
+
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_DEVICE_MANAGER_HANDLER_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/embeddedEditor.cpp b/Src/Plugins/Library/ml_devices/embeddedEditor.cpp
new file mode 100644
index 00000000..a465cc07
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/embeddedEditor.cpp
@@ -0,0 +1,1240 @@
+#include "main.h"
+#include "./embeddedEditor.h"
+#include <vector>
+
+#define EMBEDDEDEDITOR_FRAME_LEFT 1
+#define EMBEDDEDEDITOR_FRAME_TOP 1
+#define EMBEDDEDEDITOR_FRAME_RIGHT 1
+#define EMBEDDEDEDITOR_FRAME_BOTTOM 1
+
+#define EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_LEFT 1
+#define EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_TOP 1
+#define EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_RIGHT 1
+#define EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_BOTTOM 1
+
+#define EMBEDDEDEDITOR_FRAME_INNER_SPACE_LEFT 0
+#define EMBEDDEDEDITOR_FRAME_INNER_SPACE_TOP 0
+#define EMBEDDEDEDITOR_FRAME_INNER_SPACE_RIGHT 0
+#define EMBEDDEDEDITOR_FRAME_INNER_SPACE_BOTTOM 0
+
+#define EMBEDDEDEDITOR_MARGIN_LEFT 4
+#define EMBEDDEDEDITOR_MARGIN_TOP 1
+#define EMBEDDEDEDITOR_MARGIN_RIGHT 4
+#define EMBEDDEDEDITOR_MARGIN_BOTTOM 1
+
+
+#define EMBEDDEDEDITOR_BORDER_LEFT (EMBEDDEDEDITOR_FRAME_LEFT + \
+ EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_LEFT + \
+ EMBEDDEDEDITOR_FRAME_INNER_SPACE_LEFT)
+
+#define EMBEDDEDEDITOR_BORDER_TOP (EMBEDDEDEDITOR_FRAME_TOP + \
+ EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_TOP + \
+ EMBEDDEDEDITOR_FRAME_INNER_SPACE_TOP)
+
+#define EMBEDDEDEDITOR_BORDER_RIGHT (EMBEDDEDEDITOR_FRAME_RIGHT + \
+ EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_RIGHT + \
+ EMBEDDEDEDITOR_FRAME_INNER_SPACE_RIGHT)
+
+#define EMBEDDEDEDITOR_BORDER_BOTTOM (EMBEDDEDEDITOR_FRAME_BOTTOM + \
+ EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_BOTTOM + \
+ EMBEDDEDEDITOR_FRAME_INNER_SPACE_BOTTOM)
+
+
+typedef struct EmbeddedEditor
+{
+ WNDPROC originalProc;
+ COLORREF textColor;
+ COLORREF backColor;
+ COLORREF borderColor;
+ HBRUSH backBrush;
+ HBRUSH borderBrush;
+ EmbeddedEditorFinishCb callback;
+ void *user;
+ POINT anchorPoint;
+ SIZE maximumSize;
+ wchar_t *buffer;
+ size_t bufferSize;
+ long spacing;
+ long lineHeight;
+} EmbeddedEditor;
+
+typedef std::vector<HWND> WindowList;
+
+typedef struct EmbeddedEditorParent
+{
+ WNDPROC originalProc;
+ WindowList editorList;
+} EmbeddedEditorParent;
+
+
+typedef struct EmbeddedEditorThread
+{
+ HHOOK hook;
+ HWND window;
+} EmbeddedEditorThread;
+
+static size_t editorTls = ((size_t)-1);
+
+static ATOM EMBEDDEDEDITOR_PROP = 0;
+
+#define EMBEDDEDEDITOR(_hwnd) ((EmbeddedEditor*)GetPropW((_hwnd), MAKEINTATOM(EMBEDDEDEDITOR_PROP)))
+#define EMBEDDEDEDITOR_RET_VOID(_self, _hwnd) { (_self) = EMBEDDEDEDITOR((_hwnd)); if (NULL == (_self)) return; }
+#define EMBEDDEDEDITOR_RET_VAL(_self, _hwnd, _error) { (_self) = EMBEDDEDEDITOR((_hwnd)); if (NULL == (_self)) return (_error); }
+
+#define EMBEDDEDEDITOR_PARENT(_hwnd) ((EmbeddedEditorParent*)GetPropW((_hwnd), MAKEINTATOM(EMBEDDEDEDITOR_PROP)))
+
+static LRESULT WINAPI
+EmbeddedEditor_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+static LRESULT WINAPI
+EmbeddedEditorParent_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+static LRESULT CALLBACK
+EmbeddidEditorThread_MouseProc(int code, unsigned int messageId, MOUSEHOOKSTRUCT *mouse);
+
+static BOOL
+EmbeddidEditorThread_BeginMouseMonitor(HWND editorWindow)
+{
+ EmbeddedEditorThread *threadData;
+
+ if (NULL == WASABI_API_APP ||
+ NULL == editorWindow)
+ {
+ return FALSE;
+ }
+
+ if ((size_t)-1 == editorTls)
+ {
+ editorTls = WASABI_API_APP->AllocateThreadStorage();
+ if ((size_t)-1 == editorTls)
+ return FALSE;
+
+ threadData = NULL;
+ }
+ else
+ {
+ threadData = (EmbeddedEditorThread*)WASABI_API_APP->GetThreadStorage(editorTls);
+ WASABI_API_APP->SetThreadStorage(editorTls, NULL);
+ }
+
+ if (NULL != threadData)
+ {
+ if (NULL != threadData->window)
+ DestroyWindow(threadData->window);
+ }
+ else
+ {
+ threadData = (EmbeddedEditorThread*)malloc(sizeof(EmbeddedEditorThread));
+ if (NULL == threadData)
+ return FALSE;
+
+ threadData->hook = SetWindowsHookEx(WH_MOUSE, (HOOKPROC)EmbeddidEditorThread_MouseProc, NULL, GetCurrentThreadId());
+ if (NULL == threadData->hook)
+ {
+ free(threadData);
+ return FALSE;
+ }
+
+ }
+
+ threadData->window = editorWindow;
+ WASABI_API_APP->SetThreadStorage(editorTls, threadData);
+
+ return TRUE;
+}
+
+static void
+EmbeddidEditorThread_EndMouseMonitor(HWND editorWindow)
+{
+ EmbeddedEditorThread *threadData;
+
+ if (NULL == WASABI_API_APP ||
+ (size_t)-1 == editorTls ||
+ NULL == editorWindow)
+ {
+ return;
+ }
+
+ threadData = (EmbeddedEditorThread*)WASABI_API_APP->GetThreadStorage(editorTls);
+ WASABI_API_APP->SetThreadStorage(editorTls, NULL);
+
+ if (NULL != threadData)
+ {
+ if (NULL != threadData->hook)
+ UnhookWindowsHookEx(threadData->hook);
+
+ free(threadData);
+ }
+}
+
+static BOOL
+EmbeddedEditorParent_AddChild(HWND hwnd, HWND child)
+{
+ EmbeddedEditorParent *self;
+
+ if (NULL == hwnd || NULL == child)
+ return FALSE;
+
+ self = EMBEDDEDEDITOR_PARENT(hwnd);
+
+ if (NULL == self)
+ {
+ self = new EmbeddedEditorParent();
+ if (NULL == self)
+ return FALSE;
+
+ self->originalProc = (WNDPROC)(LONG_PTR)SetWindowLongPtr(hwnd, GWLP_WNDPROC,
+ (LONGX86)(LONG_PTR)EmbeddedEditorParent_WindowProc);
+
+ if (NULL != self->originalProc &&
+ FALSE == SetProp(hwnd, MAKEINTATOM(EMBEDDEDEDITOR_PROP), self))
+ {
+ SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONGX86)(LONG_PTR)self->originalProc);
+ delete self;
+ return FALSE;
+ }
+ }
+ else
+ {
+ size_t index;
+ index = self->editorList.size();
+ while(index--)
+ {
+ if (self->editorList[index] == child)
+ return FALSE;
+ }
+ }
+
+ self->editorList.push_back(child);
+ return TRUE;
+}
+
+static BOOL
+EmbeddedEditorParent_RemoveChild(HWND hwnd, HWND child)
+{
+ size_t index;
+ EmbeddedEditorParent *self;
+
+ if (NULL == hwnd || NULL == child)
+ return FALSE;
+
+ self = EMBEDDEDEDITOR_PARENT(hwnd);
+ if (NULL == self)
+ return FALSE;
+
+ index = self->editorList.size();
+ while(index--)
+ {
+ if (self->editorList[index] == child)
+ break;
+ }
+
+ if (((size_t)-1) == index)
+ return FALSE;
+
+ self->editorList.erase(self->editorList.begin() + index);
+ if (0 == self->editorList.size())
+ {
+ RemoveProp(hwnd, MAKEINTATOM(EMBEDDEDEDITOR_PROP));
+
+ if (NULL != self->originalProc)
+ SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONGX86)(LONG_PTR)self->originalProc);
+
+ delete self;
+ }
+
+ return TRUE;
+
+}
+
+BOOL
+EmbeddedEditor_Attach(HWND hwnd, EmbeddedEditorFinishCb callback, void *user)
+{
+ HWND parent;
+ EmbeddedEditor *self;
+
+ if (NULL == hwnd)
+ return FALSE;
+
+ if (0 == EMBEDDEDEDITOR_PROP)
+ {
+ EMBEDDEDEDITOR_PROP = GlobalAddAtom(TEXT("EmdeddedEditorProp"));
+ if (0 == EMBEDDEDEDITOR_PROP)
+ return FALSE;
+ }
+
+ self = (EmbeddedEditor*)malloc(sizeof(EmbeddedEditor));
+ if (NULL == self)
+ return FALSE;
+
+ memset(self, 0, sizeof(EmbeddedEditor));
+
+ self->originalProc = (WNDPROC)(LONG_PTR)SetWindowLongPtr(hwnd, GWLP_WNDPROC,
+ (LONGX86)(LONG_PTR)EmbeddedEditor_WindowProc);
+
+ if (NULL != self->originalProc &&
+ FALSE == SetProp(hwnd, MAKEINTATOM(EMBEDDEDEDITOR_PROP), self))
+ {
+ SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONGX86)(LONG_PTR)self->originalProc);
+ free(self);
+ return FALSE;
+ }
+
+ self->callback = callback;
+ self->user = user;
+
+ self->backColor = RGB(0, 0, 255); //GetSysColor(COLOR_WINDOW);
+ self->backBrush = NULL;
+ self->textColor = RGB(255, 255, 0); //GetSysColor(COLOR_WINDOWTEXT);
+ self->borderColor = RGB(255, 0, 0); //GetSysColor(COLOR_WINDOWFRAME);
+ self->borderBrush = NULL;
+ self->spacing = -1;
+ self->lineHeight = -1;
+
+ parent = GetAncestor(hwnd, GA_PARENT);
+ if(NULL != parent)
+ EmbeddedEditorParent_AddChild(parent, hwnd);
+
+ EmbeddidEditorThread_BeginMouseMonitor(hwnd);
+
+ RECT rect, formatRect;
+ GetWindowRect(hwnd, &rect);
+ SetRect(&formatRect, 0, 0, RECTWIDTH(rect) - 6, RECTHEIGHT(rect) - 6);
+ SendMessage(hwnd, EM_SETRECTNP, 0, (LPARAM)&formatRect);
+
+ SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_FRAMECHANGED);
+
+
+ return TRUE;
+}
+
+BOOL
+EmbeddedEditor_AdjustWindowRectEx(RECT *rect, unsigned long styleEx, unsigned long style)
+{
+ if (NULL == rect)
+ return FALSE;
+
+ if (0 != ((WS_EX_STATICEDGE | WS_EX_CLIENTEDGE) & styleEx) ||
+ 0 != (WS_BORDER & style))
+ {
+ rect->left -= (EMBEDDEDEDITOR_BORDER_LEFT + EMBEDDEDEDITOR_MARGIN_LEFT);
+ rect->top -= (EMBEDDEDEDITOR_BORDER_TOP + EMBEDDEDEDITOR_MARGIN_TOP);
+ rect->right += (EMBEDDEDEDITOR_BORDER_RIGHT + EMBEDDEDEDITOR_MARGIN_RIGHT);
+ rect->bottom += (EMBEDDEDEDITOR_BORDER_BOTTOM + EMBEDDEDEDITOR_MARGIN_BOTTOM);
+ }
+
+ return TRUE;
+}
+
+static HBRUSH
+EmbeddedEditor_GetBackBrush(EmbeddedEditor *self)
+{
+ if (NULL == self->backBrush)
+ self->backBrush = CreateSolidBrush(self->backColor);
+
+ return self->backBrush;
+}
+
+static HBRUSH
+EmbeddedEditor_GetBorderBrush(EmbeddedEditor *self)
+{
+ if (NULL == self->borderBrush)
+ self->borderBrush = CreateSolidBrush(self->borderColor);
+
+ return self->borderBrush;
+}
+
+static BOOL
+EmbdeddedEditor_GetBorderEnabled(HWND hwnd)
+{
+ unsigned long style;
+
+ style = GetWindowStyleEx(hwnd);
+ if (0 != ((WS_EX_STATICEDGE | WS_EX_CLIENTEDGE) & style))
+ return TRUE;
+
+ style = GetWindowStyle(hwnd);
+ if (0 != (WS_BORDER & style))
+ return TRUE;
+
+ return FALSE;
+}
+
+
+static BOOL
+EmbdeddedEditor_DrawBorder(EmbeddedEditor *self, HWND hwnd, HDC hdc)
+{
+ RECT windowRect, clientRect;
+ HRGN borderRgn;
+ POINT polygons[16] = {0};
+ const int polygonsPointCount[] = {4, 4, 4, 4};
+
+ if (FALSE == GetWindowRect(hwnd, &windowRect) ||
+ FALSE == GetClientRect(hwnd, &clientRect))
+ {
+ return FALSE;
+ }
+
+ MapWindowPoints(hwnd, HWND_DESKTOP, (POINT*)&clientRect, 2);
+
+ OffsetRect(&clientRect, -windowRect.left, -windowRect.top);
+ OffsetRect(&windowRect, -windowRect.left, -windowRect.top);
+
+ MakeRectPolygon(&polygons[0], windowRect.left, windowRect.top, windowRect.right, clientRect.top);
+ MakeRectPolygon(&polygons[4], clientRect.right, clientRect.top, windowRect.right, clientRect.bottom);
+ MakeRectPolygon(&polygons[8], windowRect.left, clientRect.bottom, windowRect.right, windowRect.bottom);
+ MakeRectPolygon(&polygons[12], windowRect.left, clientRect.top, clientRect.left, clientRect.bottom);
+
+ borderRgn = CreatePolyPolygonRgn(polygons,
+ polygonsPointCount,
+ ARRAYSIZE(polygonsPointCount),
+ WINDING);
+
+ if (NULL != borderRgn)
+ {
+ MakeRectPolygon(&polygons[0],
+ windowRect.left + EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_LEFT,
+ windowRect.top + EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_TOP,
+ windowRect.right - EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_RIGHT,
+ windowRect.top + (EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_TOP + EMBEDDEDEDITOR_FRAME_TOP));
+ MakeRectPolygon(&polygons[4],
+ windowRect.right - (EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_RIGHT + EMBEDDEDEDITOR_FRAME_RIGHT),
+ windowRect.top + (EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_TOP + EMBEDDEDEDITOR_FRAME_TOP),
+ windowRect.right - EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_RIGHT,
+ windowRect.bottom - (EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_BOTTOM + EMBEDDEDEDITOR_FRAME_BOTTOM));
+ MakeRectPolygon(&polygons[8],
+ windowRect.left + EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_LEFT,
+ windowRect.bottom - (EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_BOTTOM + EMBEDDEDEDITOR_FRAME_BOTTOM),
+ windowRect.right - EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_RIGHT,
+ windowRect.bottom - EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_BOTTOM);
+ MakeRectPolygon(&polygons[12],
+ windowRect.left + EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_LEFT,
+ windowRect.top + (EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_TOP + EMBEDDEDEDITOR_FRAME_TOP),
+ windowRect.left + (EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_LEFT + EMBEDDEDEDITOR_FRAME_LEFT),
+ windowRect.bottom - (EMBEDDEDEDITOR_FRAME_OUTTER_SPACE_BOTTOM + EMBEDDEDEDITOR_FRAME_BOTTOM));
+
+ HRGN frameRgn = CreatePolyPolygonRgn(polygons,
+ polygonsPointCount,
+ ARRAYSIZE(polygonsPointCount),
+ WINDING);
+ if (NULL != frameRgn)
+ {
+ int combineResult = CombineRgn(frameRgn, borderRgn, frameRgn, RGN_AND);
+ if (NULLREGION != combineResult && ERROR != combineResult)
+ {
+ FillRgn(hdc, frameRgn, EmbeddedEditor_GetBorderBrush(self));
+ combineResult = CombineRgn(borderRgn, borderRgn, frameRgn, RGN_DIFF);
+ }
+ else
+ combineResult = COMPLEXREGION;
+
+ if (NULLREGION != combineResult && ERROR != combineResult)
+ FillRgn(hdc, borderRgn, EmbeddedEditor_GetBackBrush(self));
+
+ DeleteObject(frameRgn);
+ }
+ DeleteObject(borderRgn);
+ }
+
+
+
+ return TRUE;
+}
+
+static BOOL
+EmbeddedEditor_InvokeCallback(EmbeddedEditor *self, HWND hwnd, BOOL cancelMode, BOOL removeCallback)
+{
+ wchar_t *text;
+ unsigned int length;
+ EmbeddedEditorFinishCb callback;
+
+ if (NULL == self || NULL == self->callback)
+ return FALSE;
+
+
+ length = (unsigned int)SendMessage(hwnd, WM_GETTEXTLENGTH, 0, 0L);
+ text = String_Malloc(++length);
+ if (NULL == text)
+ return FALSE;
+
+ SendMessage(hwnd, WM_GETTEXT, (WPARAM)length, (LPARAM)text);
+
+ callback = self->callback;
+ if (FALSE != removeCallback)
+ self->callback = NULL;
+
+ callback(hwnd, cancelMode, text, self->user);
+
+ return TRUE;
+}
+
+
+static const wchar_t *
+EmbeddedEditor_GetWindowText(EmbeddedEditor *self, HWND hwnd, size_t *lengthOut)
+{
+ size_t length;
+
+ length = (size_t)SendMessage(hwnd, WM_GETTEXTLENGTH, 0, 0L);
+ if (length >= self->bufferSize)
+ {
+ size_t size;
+
+ size = (((length + 1)/128) + 1) * 128;
+
+ String_Free(self->buffer);
+ self->buffer = String_Malloc(size);
+ if (NULL == self->buffer)
+ return FALSE;
+
+ self->bufferSize = size;
+ }
+
+ if (0 != length)
+ length = SendMessage(hwnd, WM_GETTEXT, (WPARAM)self->bufferSize, (LPARAM)self->buffer);
+ else
+ self->buffer[0] = L'\0';
+
+ if (NULL != lengthOut)
+ *lengthOut = length;
+
+ return self->buffer;
+}
+
+static BOOL
+EmbeddedEditor_Resize(EmbeddedEditor *self, HWND hwnd, BOOL redraw)
+{
+ size_t textLength;
+ const wchar_t *text;
+ HDC hdc;
+ SIZE maximumSize;
+ BOOL borderEnabled;
+ HWND parentWindow;
+ RECT rect, parentRect, marginRect;
+ SIZE parentSize;
+ BOOL result;
+ unsigned int windowStyle;
+ HFONT font, prevFont;
+
+ parentWindow = GetAncestor(hwnd, GA_PARENT);
+ if (NULL == parentWindow ||
+ FALSE == GetClientRect(parentWindow, &parentRect))
+ {
+ return FALSE;
+ }
+
+ parentSize.cx = RECTWIDTH(parentRect);
+ parentSize.cy = RECTHEIGHT(parentRect);
+
+ windowStyle = GetWindowStyle(hwnd);
+ borderEnabled = EmbdeddedEditor_GetBorderEnabled(hwnd);
+
+ text = EmbeddedEditor_GetWindowText(self, hwnd, &textLength);
+ if (NULL == text)
+ return FALSE;
+
+ hdc = GetDCEx(hwnd, NULL, DCX_CACHE | DCX_NORESETATTRS);
+ if (NULL == hdc)
+ return FALSE;
+
+ result = FALSE;
+
+ font = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0L);
+ prevFont = SelectFont(hdc, font);
+
+ if (-1 == self->spacing ||
+ -1 == self->lineHeight)
+ {
+ TEXTMETRIC textMetrics;
+ if (FALSE != GetTextMetrics(hdc, &textMetrics))
+ {
+ self->spacing = textMetrics.tmAveCharWidth;
+ self->lineHeight = textMetrics.tmHeight;
+ }
+ }
+
+ if (0 == (ES_MULTILINE & windowStyle))
+ {
+ unsigned long margins;
+ margins = (unsigned long)SendMessage(hwnd, EM_GETMARGINS, 0, 0L);
+ SetRect(&marginRect, (short)LOWORD(margins), 0, (short)HIWORD(margins), 0);
+ }
+ else
+ {
+ RECT clientRect;
+ if (FALSE == GetClientRect(hwnd, &clientRect))
+ SetRectEmpty(&clientRect);
+
+ if (clientRect.right < clientRect.left)
+ clientRect.right = clientRect.left;
+ if (clientRect.bottom < (clientRect.top + self->lineHeight))
+ clientRect.bottom = clientRect.top + self->lineHeight;
+
+ SendMessage(hwnd, EM_GETRECT, 0, (LPARAM)&marginRect);
+
+ marginRect.left = (marginRect.left > clientRect.left) ?
+ (marginRect.left - clientRect.left) : 1;
+
+ marginRect.top = (marginRect.top > clientRect.top) ?
+ (marginRect.top - clientRect.top) : 1;
+
+ marginRect.right = (marginRect.right < clientRect.right) ?
+ (clientRect.right - marginRect.right) : 1;
+
+ marginRect.bottom = (marginRect.bottom < clientRect.bottom) ?
+ (clientRect.bottom - marginRect.bottom) : 1;
+
+ if (SendMessage(hwnd, EM_GETLINECOUNT, 0, 0L) > 1)
+ {
+ if (marginRect.bottom >= (self->lineHeight - 1))
+ marginRect.bottom -= (self->lineHeight - 2);
+ }
+
+ SetRect(&marginRect, EMBEDDEDEDITOR_MARGIN_LEFT, EMBEDDEDEDITOR_MARGIN_TOP,
+ EMBEDDEDEDITOR_MARGIN_RIGHT, EMBEDDEDEDITOR_MARGIN_BOTTOM);
+ }
+
+ maximumSize.cx = (self->maximumSize.cx > 0) ? MIN(parentSize.cx, self->maximumSize.cx) : parentSize.cx;
+ maximumSize.cx -= (marginRect.left + marginRect.right);
+ if (FALSE != borderEnabled)
+ maximumSize.cx -= (EMBEDDEDEDITOR_BORDER_LEFT + EMBEDDEDEDITOR_BORDER_RIGHT);
+
+ if (maximumSize.cx < 0)
+ maximumSize.cx = 0;
+
+ maximumSize.cy = (self->maximumSize.cy > 0) ? MIN(parentSize.cy, self->maximumSize.cy) : parentSize.cy;
+ maximumSize.cy -= (marginRect.top + marginRect.bottom);
+ if (FALSE != borderEnabled)
+ maximumSize.cy -= (EMBEDDEDEDITOR_BORDER_TOP + EMBEDDEDEDITOR_BORDER_BOTTOM);
+
+ if (maximumSize.cy < 0)
+ maximumSize.cy = 0;
+
+ if (0 == textLength)
+ {
+ SetRectEmpty(&rect);
+ result = TRUE;
+ }
+ else
+ {
+ SetRect(&rect, 0, 0, maximumSize.cx, maximumSize.cy);
+ result = DrawText(hdc, text, (int)textLength, &rect,
+ DT_CALCRECT | DT_LEFT | DT_TOP | DT_WORDBREAK |
+ DT_EDITCONTROL | DT_NOPREFIX);
+ }
+
+ SelectFont(hdc, prevFont);
+ ReleaseDC(hwnd, hdc);
+
+
+ if (FALSE != result)
+ {
+ unsigned int flags;
+
+ rect.right += 2 * self->spacing;
+
+ if (RECTHEIGHT(rect) < self->lineHeight)
+ rect.bottom = rect.top + self->lineHeight;
+ else if (RECTHEIGHT(rect) > self->lineHeight)
+ rect.right = rect.left + maximumSize.cx;
+
+ rect.right += (marginRect.left + marginRect.right);
+ rect.bottom += (marginRect.top + marginRect.bottom);
+
+ if (FALSE != borderEnabled)
+ {
+ rect.right += (EMBEDDEDEDITOR_BORDER_LEFT + EMBEDDEDEDITOR_BORDER_RIGHT);
+ rect.bottom += (EMBEDDEDEDITOR_BORDER_TOP + EMBEDDEDEDITOR_BORDER_BOTTOM);
+ }
+
+ flags = SWP_NOACTIVATE | SWP_NOZORDER;
+ if (FALSE == redraw)
+ flags |= SWP_NOREDRAW;
+
+ OffsetRect(&rect, self->anchorPoint.x, self->anchorPoint.y);
+
+ long offsetX = 0, offsetY = 0;
+
+ if (0 != (ES_CENTER & windowStyle))
+ {
+ if (self->maximumSize.cx > 0)
+ {
+ offsetX += (self->maximumSize.cx - RECTWIDTH(rect))/2;
+ }
+ }
+
+ if (rect.right > parentSize.cx)
+ offsetX -= (rect.right - parentSize.cx);
+
+ if (rect.bottom > parentSize.cy)
+ offsetY -= (rect.bottom - parentSize.cy);
+
+ OffsetRect(&rect, offsetX, offsetY);
+
+ result = SetWindowPos(hwnd, NULL, rect.left, rect.top, RECTWIDTH(rect), RECTHEIGHT(rect), flags);
+ }
+
+ return result;
+}
+
+static void
+EmbdeddedEditor_OnDestroy(EmbeddedEditor *self, HWND hwnd)
+{
+ HWND parent;
+
+ if (NULL != self &&
+ NULL != self->callback)
+ {
+ EmbeddedEditor_InvokeCallback(self, hwnd, TRUE, TRUE);
+ }
+
+ RemoveProp(hwnd, MAKEINTATOM(EMBEDDEDEDITOR_PROP));
+ if (NULL == self)
+ return;
+
+ if (NULL != self->originalProc)
+ {
+ SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONGX86)(LONG_PTR)self->originalProc);
+ CallWindowProc(self->originalProc, hwnd, WM_DESTROY, 0, 0L);
+ }
+
+ if(NULL != self->backBrush)
+ DeleteObject(self->backBrush);
+
+ if (NULL != self->borderBrush)
+ DeleteObject(self->borderBrush);
+
+ String_Free(self->buffer);
+
+ free(self);
+
+ parent = GetAncestor(hwnd, GA_PARENT);
+ if(NULL != parent)
+ EmbeddedEditorParent_RemoveChild(parent, hwnd);
+
+ EmbeddidEditorThread_EndMouseMonitor(hwnd);
+}
+
+static BOOL
+EmbdeddedEditor_OnNcCalcSize(EmbeddedEditor *self, HWND hwnd, BOOL calcValidRects,
+ NCCALCSIZE_PARAMS *params, LRESULT *result)
+{
+
+ if (FALSE != EmbdeddedEditor_GetBorderEnabled(hwnd))
+ {
+ if (FALSE != calcValidRects)
+ {
+ SetRect(&params->rgrc[0],
+ params->lppos->x + EMBEDDEDEDITOR_BORDER_LEFT,
+ params->lppos->y + EMBEDDEDEDITOR_BORDER_TOP,
+ params->lppos->x + params->lppos->cx - EMBEDDEDEDITOR_BORDER_RIGHT,
+ params->lppos->y + params->lppos->cy - EMBEDDEDEDITOR_BORDER_BOTTOM);
+
+ *result = WVR_ALIGNTOP | WVR_ALIGNLEFT;
+ }
+ else
+ {
+ if (FALSE != GetWindowRect(hwnd, &params->rgrc[0]))
+ {
+ params->rgrc[0].left += EMBEDDEDEDITOR_BORDER_LEFT;
+ params->rgrc[0].top += EMBEDDEDEDITOR_BORDER_TOP;
+ params->rgrc[0].right -= EMBEDDEDEDITOR_BORDER_RIGHT;
+ params->rgrc[0].bottom -= EMBEDDEDEDITOR_BORDER_BOTTOM;
+ }
+ }
+ }
+
+ return TRUE;
+}
+static BOOL
+EmbdeddedEditor_OnNcPaint(EmbeddedEditor *self, HWND hwnd, HRGN updateRegion)
+{
+ BOOL result;
+
+ if (FALSE != EmbdeddedEditor_GetBorderEnabled(hwnd))
+ {
+ HDC hdc;
+ unsigned int flags;
+
+ flags = DCX_PARENTCLIP | DCX_CACHE | DCX_WINDOW | DCX_CLIPSIBLINGS |
+ DCX_INTERSECTUPDATE | DCX_VALIDATE;
+
+ hdc = GetDCEx(hwnd, ((HRGN)NULLREGION != updateRegion) ? updateRegion : NULL, flags);
+ if (NULL != hdc)
+ {
+ result = EmbdeddedEditor_DrawBorder(self, hwnd, hdc);
+ ReleaseDC(hwnd, hdc);
+ }
+ else
+ result = FALSE;
+ }
+ else
+ result = TRUE;
+
+ return result;
+}
+
+static BOOL
+EmbeddedEditor_OnSetCursor(EmbeddedEditor *self, HWND hwnd, HWND cursorWindow, int hitTest, int mouseMessage, LRESULT *result)
+{
+ HCURSOR cursor;
+
+ if (cursorWindow != hwnd ||
+ HTCLIENT != hitTest)
+ {
+ return FALSE;
+ }
+
+ cursor = LoadCursor(NULL, IDC_IBEAM);
+ if (NULL == cursor)
+ return FALSE;
+
+ SetCursor(cursor);
+
+ *result = TRUE;
+ return TRUE;
+}
+
+
+static BOOL
+EmbeddedEditor_OnGetDlgCode(EmbeddedEditor *self, HWND hwnd, unsigned int vKey, MSG *message, LRESULT *result)
+{
+ if (NULL != message)
+ {
+ switch(vKey)
+ {
+ case VK_TAB:
+ case VK_ESCAPE:
+ EmbeddedEditor_InvokeCallback(self, hwnd, TRUE, TRUE);
+ DestroyWindow(hwnd);
+ *result = DLGC_WANTMESSAGE;
+ return TRUE;
+ case VK_RETURN:
+ EmbeddedEditor_InvokeCallback(self, hwnd, FALSE, TRUE);
+ DestroyWindow(hwnd);
+ *result = DLGC_WANTMESSAGE;
+ return TRUE;
+ }
+ }
+
+ if (NULL != self->originalProc)
+ *result = CallWindowProc(self->originalProc, hwnd, WM_GETDLGCODE, (WPARAM)vKey, (LPARAM)message);
+ else
+ *result = 0;
+
+ if (NULL == message)
+ *result |= DLGC_WANTMESSAGE;
+
+
+ return TRUE;
+}
+
+static BOOL
+EmbeddedEditor_OnKillFocus(EmbeddedEditor *self, HWND hwnd, HWND focusedWindow)
+{
+ EmbeddedEditor_InvokeCallback(self, hwnd, FALSE, TRUE);
+ DestroyWindow(hwnd);
+ return TRUE;
+}
+
+static BOOL
+EmbeddedEditor_OnMouseWheel(EmbeddedEditor *self, HWND hwnd, int virtualKeys, int distance, long pointer_s)
+{
+ HWND parentWindow;
+
+ parentWindow = GetAncestor(hwnd, GA_PARENT);
+
+ EmbeddedEditor_InvokeCallback(self, hwnd, TRUE, TRUE);
+ DestroyWindow(hwnd);
+
+ if (NULL != parentWindow)
+ {
+ SendMessage(parentWindow, WM_MOUSEWHEEL,
+ MAKEWPARAM(virtualKeys, distance), (LPARAM)pointer_s);
+ }
+
+ return TRUE;
+}
+
+static void
+EmbeddedEditor_OnCommand(EmbeddedEditor *self, HWND hwnd, int eventId)
+{
+ switch(eventId)
+ {
+ case EN_UPDATE:
+ EmbeddedEditor_Resize(self, hwnd, TRUE);
+ break;
+ }
+}
+static BOOL
+EmbeddedEditor_OnSetFont(EmbeddedEditor *self, HWND hwnd, HFONT font, BOOL redraw)
+{
+ if (NULL != self->originalProc)
+ CallWindowProc(self->originalProc, hwnd, WM_SETFONT, (WPARAM)font, MAKELPARAM(redraw, 0));
+
+ self->spacing = -1;
+ self->lineHeight = -1;
+
+ EmbeddedEditor_Resize(self, hwnd, redraw);
+ return TRUE;
+}
+
+
+static BOOL
+EmbeddedEditor_OnWindowPosChanged(EmbeddedEditor *self, HWND hwnd, WINDOWPOS *pwp)
+{
+ if (SWP_NOSIZE != ((SWP_NOSIZE | SWP_FRAMECHANGED) & pwp->flags))
+ {
+ RECT formatRect;
+ if (FALSE != GetClientRect(hwnd, &formatRect))
+ {
+ formatRect.left += EMBEDDEDEDITOR_MARGIN_LEFT;
+ formatRect.top += EMBEDDEDEDITOR_MARGIN_TOP;
+ formatRect.right -= EMBEDDEDEDITOR_MARGIN_RIGHT;
+ formatRect.bottom -= EMBEDDEDEDITOR_MARGIN_BOTTOM;
+
+ if (formatRect.right < formatRect.left)
+ formatRect.right = formatRect.left;
+
+ if (formatRect.bottom < formatRect.top)
+ formatRect.bottom = formatRect.top;
+
+ SendMessage(hwnd, EM_SETRECTNP, 0, (LPARAM)&formatRect);
+ }
+ }
+
+ if (NULL != self->originalProc)
+ CallWindowProc(self->originalProc, hwnd, WM_WINDOWPOSCHANGED, 0, (LPARAM)pwp);
+
+ return TRUE;
+}
+
+static void
+EmbeddedEditor_OnSetTextColor(EmbeddedEditor *self, HWND hwnd, COLORREF color, LRESULT *result)
+{
+ *result = (LRESULT)self->textColor;
+ if (self->textColor != color)
+ self->textColor = color;
+}
+
+static void
+EmbeddedEditor_OnGetTextColor(EmbeddedEditor *self, HWND hwnd, LRESULT *result)
+{
+ *result = (LRESULT)self->textColor;
+}
+
+static void
+EmbeddedEditor_OnSetBackColor(EmbeddedEditor *self, HWND hwnd, COLORREF color, LRESULT *result)
+{
+ *result = (LRESULT)self->backColor;
+ if (self->backColor != color)
+ {
+ self->backColor = color;
+ if (NULL != self->backBrush)
+ {
+ DeleteObject(self->backBrush);
+ self->backBrush = NULL;
+ }
+ }
+}
+
+static void
+EmbeddedEditor_OnGetBackColor(EmbeddedEditor *self, HWND hwnd, LRESULT *result)
+{
+ *result = (LRESULT)self->backColor;
+}
+
+static void
+EmbeddedEditor_OnSetBorderColor(EmbeddedEditor *self, HWND hwnd, COLORREF color, LRESULT *result)
+{
+ *result = (LRESULT)self->borderColor;
+ if (self->borderColor != color)
+ {
+ self->borderColor = color;
+ if (NULL != self->borderBrush)
+ {
+ DeleteObject(self->borderBrush);
+ self->borderBrush = NULL;
+ }
+ }
+}
+
+static void
+EmbeddedEditor_OnGetBorderColor(EmbeddedEditor *self, HWND hwnd, LRESULT *result)
+{
+ *result = (LRESULT)self->borderColor;
+}
+
+static void
+EmbeddedEditor_OnSetUserData(EmbeddedEditor *self, HWND hwnd, void *user, LRESULT *result)
+{
+ *result = (LRESULT)self->user;
+ self->user = user;
+}
+
+static void
+EmbeddedEditor_OnGetUserData(EmbeddedEditor *self, HWND hwnd, LRESULT *result)
+{
+ *result = (LRESULT)self->user;
+}
+
+static void
+EmbeddedEditor_OnSetAnchorPoint(EmbeddedEditor *self, HWND hwnd, long x, long y, LRESULT *result)
+{
+ self->anchorPoint.x = x;
+ self->anchorPoint.y = y;
+ *result = TRUE;
+}
+
+static void
+EmbeddedEditor_OnGetAnchorPoint(EmbeddedEditor *self, HWND hwnd, long *x, long *y, LRESULT *result)
+{
+ if (NULL != x)
+ *x = self->anchorPoint.x;
+
+ if (NULL != y)
+ *y = self->anchorPoint.y;
+
+ *result = TRUE;
+}
+
+static void
+EmbeddedEditor_OnSetMaxSize(EmbeddedEditor *self, HWND hwnd, long width, long height, LRESULT *result)
+{
+ self->maximumSize.cx = width;
+ self->maximumSize.cy = height;
+ *result = TRUE;
+}
+
+static void
+EmbeddedEditor_OnGetMaxSize(EmbeddedEditor *self, HWND hwnd, long *width, long *height, LRESULT *result)
+{
+ if (NULL != width)
+ *width = self->maximumSize.cx;
+
+ if (NULL != height)
+ *height = self->maximumSize.cy;
+
+ *result = TRUE;
+}
+
+static void
+EmbeddedEditor_OnEndEditing(EmbeddedEditor *self, HWND hwnd, BOOL cancel)
+{
+ EmbeddedEditor_InvokeCallback(self, hwnd, cancel, TRUE);
+ DestroyWindow(hwnd);
+}
+
+static BOOL
+EmbeddedEditor_MessageProc(EmbeddedEditor *self, HWND hwnd, UINT uMsg,
+ WPARAM wParam, LPARAM lParam, LRESULT *result)
+{
+
+ switch(uMsg)
+ {
+ case WM_DESTROY:
+ EmbdeddedEditor_OnDestroy(self, hwnd);
+ return TRUE;
+ case WM_NCCALCSIZE:
+ return EmbdeddedEditor_OnNcCalcSize(self, hwnd, (BOOL)wParam, (NCCALCSIZE_PARAMS*)lParam, result);
+ case WM_NCPAINT:
+ return EmbdeddedEditor_OnNcPaint(self, hwnd, (HRGN)wParam);
+ case WM_SETCURSOR:
+ return EmbeddedEditor_OnSetCursor(self, hwnd, (HWND)wParam,
+ LOWORD(lParam), HIWORD(lParam), result);
+ case WM_GETDLGCODE:
+ return EmbeddedEditor_OnGetDlgCode(self, hwnd, (unsigned int)wParam, (MSG*)lParam, result);
+ case WM_KILLFOCUS:
+ EmbeddedEditor_OnKillFocus(self, hwnd, (HWND)wParam);
+ return 0;
+ case WM_MOUSEWHEEL:
+ return EmbeddedEditor_OnMouseWheel(self, hwnd, LOWORD(wParam), (short)HIWORD(wParam), (LONG)lParam);
+ case WM_SETFONT:
+ return EmbeddedEditor_OnSetFont(self, hwnd, (HFONT)wParam, (BOOL)LOWORD(lParam));
+ case WM_WINDOWPOSCHANGED:
+ return EmbeddedEditor_OnWindowPosChanged(self, hwnd, (WINDOWPOS*)lParam);
+
+ case EMBEDDEDEDITOR_WM_SET_TEXT_COLOR: EmbeddedEditor_OnSetTextColor(self, hwnd, (COLORREF)lParam, result); return TRUE;
+ case EMBEDDEDEDITOR_WM_GET_TEXT_COLOR: EmbeddedEditor_OnGetTextColor(self, hwnd, result); return TRUE;
+ case EMBEDDEDEDITOR_WM_SET_BACK_COLOR: EmbeddedEditor_OnSetBackColor(self, hwnd, (COLORREF)lParam, result); return TRUE;
+ case EMBEDDEDEDITOR_WM_GET_BACK_COLOR: EmbeddedEditor_OnGetBackColor(self, hwnd, result); return TRUE;
+ case EMBEDDEDEDITOR_WM_SET_BORDER_COLOR: EmbeddedEditor_OnSetBorderColor(self, hwnd, (COLORREF)lParam, result); return TRUE;
+ case EMBEDDEDEDITOR_WM_GET_BORDER_COLOR: EmbeddedEditor_OnGetBorderColor(self, hwnd, result); return TRUE;
+ case EMBEDDEDEDITOR_WM_SET_USER_DATA: EmbeddedEditor_OnSetUserData(self, hwnd, (void*)lParam, result); return TRUE;
+ case EMBEDDEDEDITOR_WM_GET_USER_DATA: EmbeddedEditor_OnGetUserData(self, hwnd, result); return TRUE;
+ case EMBEDDEDEDITOR_WM_SET_ANCHOR_POINT: EmbeddedEditor_OnSetAnchorPoint(self, hwnd, (long)wParam, (long)lParam, result); return TRUE;
+ case EMBEDDEDEDITOR_WM_GET_ANCHOR_POINT: EmbeddedEditor_OnGetAnchorPoint(self, hwnd, (long*)wParam, (long*)lParam, result); return TRUE;
+ case EMBEDDEDEDITOR_WM_SET_MAX_SIZE: EmbeddedEditor_OnSetMaxSize(self, hwnd, (long)wParam, (long)lParam, result); return TRUE;
+ case EMBEDDEDEDITOR_WM_GET_MAX_SIZE: EmbeddedEditor_OnGetMaxSize(self, hwnd, (long*)wParam, (long*)lParam, result); return TRUE;
+ case EMBEDDEDEDITOR_WM_END_EDITING: EmbeddedEditor_OnEndEditing(self, hwnd, (BOOL)wParam); return TRUE;
+
+ }
+ return FALSE;
+}
+
+static LRESULT WINAPI
+EmbeddedEditor_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ EmbeddedEditor *self;
+ LRESULT result;
+
+ self = EMBEDDEDEDITOR(hwnd);
+
+ if (NULL == self ||
+ NULL == self->originalProc)
+ {
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+ }
+
+ result = 0;
+ if (FALSE != EmbeddedEditor_MessageProc(self, hwnd, uMsg, wParam, lParam, &result))
+ {
+ return result;
+ }
+
+ return CallWindowProc(self->originalProc, hwnd, uMsg, wParam, lParam);
+}
+
+
+static void
+EmbdeddedEditorParent_OnDestroy(EmbeddedEditorParent *self, HWND hwnd)
+{
+ RemoveProp(hwnd, MAKEINTATOM(EMBEDDEDEDITOR_PROP));
+ if (NULL == self)
+ return;
+
+ if (NULL != self->originalProc)
+ {
+ SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONGX86)(LONG_PTR)self->originalProc);
+ CallWindowProc(self->originalProc, hwnd, WM_DESTROY, 0, 0L);
+ }
+
+ delete self;
+}
+
+static BOOL
+EmbdeddedEditorParent_OnGetEditColors(EmbeddedEditorParent *self, HWND hwnd, HDC hdc, HWND control, LRESULT *result)
+{
+ size_t index;
+ index = self->editorList.size();
+ while(index--)
+ {
+ if (control == self->editorList[index])
+ {
+ EmbeddedEditor *editor = EMBEDDEDEDITOR(control);
+ if (NULL != editor)
+ {
+ SetTextColor(hdc, editor->textColor);
+ SetBkColor(hdc, editor->backColor);
+ *result = (LRESULT)EmbeddedEditor_GetBackBrush(editor);
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+EmbdeddedEditorParent_OnCommand(EmbeddedEditorParent *self, HWND hwnd, int commandId, int eventId, HWND control)
+{
+ size_t index;
+
+ if (NULL == control)
+ return;
+
+ index = self->editorList.size();
+ while(index--)
+ {
+ if (control == self->editorList[index])
+ {
+ EmbeddedEditor *editor = EMBEDDEDEDITOR(control);
+ if (NULL != editor)
+ {
+ EmbeddedEditor_OnCommand(editor, control, eventId);
+ }
+ }
+ }
+
+}
+
+static BOOL
+EmbeddedEditorParent_MessageProc(EmbeddedEditorParent *self, HWND hwnd, UINT uMsg,
+ WPARAM wParam, LPARAM lParam, LRESULT *result)
+{
+
+ switch(uMsg)
+ {
+ case WM_DESTROY:
+ EmbdeddedEditorParent_OnDestroy(self, hwnd);
+ return TRUE;
+
+ case WM_CTLCOLOREDIT:
+ return EmbdeddedEditorParent_OnGetEditColors(self, hwnd, (HDC)wParam, (HWND)lParam, result);
+
+ case WM_COMMAND:
+ EmbdeddedEditorParent_OnCommand(self, hwnd, LOWORD(wParam), HIWORD(wParam), (HWND)lParam);
+ break;
+ }
+ return FALSE;
+}
+
+static LRESULT WINAPI
+EmbeddedEditorParent_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ EmbeddedEditorParent *self;
+ LRESULT result;
+
+ self = EMBEDDEDEDITOR_PARENT(hwnd);
+
+ if (NULL == self ||
+ NULL == self->originalProc)
+ {
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+ }
+
+ result = 0;
+ if (FALSE != EmbeddedEditorParent_MessageProc(self, hwnd, uMsg, wParam, lParam, &result))
+ {
+ return result;
+ }
+
+ return CallWindowProc(self->originalProc, hwnd, uMsg, wParam, lParam);
+}
+
+
+static LRESULT CALLBACK
+EmbeddidEditorThread_MouseProc(int code, unsigned int messageId, MOUSEHOOKSTRUCT *mouse)
+{
+ if ((size_t)-1 != editorTls)
+ {
+ EmbeddedEditorThread *threadData;
+ threadData = (EmbeddedEditorThread*)WASABI_API_APP->GetThreadStorage(editorTls);
+
+ if (NULL != threadData)
+ {
+ LRESULT result;
+ if (NULL != threadData->hook)
+ result = CallNextHookEx(threadData->hook, code, (WPARAM)messageId, (LPARAM)mouse);
+ else
+ result = 0;
+
+ if (code >= 0)
+ {
+ if ((messageId >= WM_LBUTTONDOWN && messageId <= 0x20E && mouse->hwnd != threadData->window) ||
+ (messageId >= WM_NCLBUTTONDOWN && messageId <= 0x00AD))
+ {
+
+ HWND editorWindow;
+ editorWindow = threadData->window;
+ if (NULL != editorWindow)
+ PostMessage(editorWindow, EMBEDDEDEDITOR_WM_END_EDITING, FALSE, 0L);
+ }
+ }
+
+ return result;
+ }
+ }
+
+ return 0;
+}
diff --git a/Src/Plugins/Library/ml_devices/embeddedEditor.h b/Src/Plugins/Library/ml_devices/embeddedEditor.h
new file mode 100644
index 00000000..939348ed
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/embeddedEditor.h
@@ -0,0 +1,79 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_EMBEDED_EDITOR_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_EMBEDED_EDITOR_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+
+typedef void (CALLBACK *EmbeddedEditorFinishCb)(HWND /*editorWindow*/, BOOL /*canceled*/,
+ const wchar_t * /*text*/, void * /*user*/);
+
+
+BOOL
+EmbeddedEditor_Attach(HWND hwnd,
+ EmbeddedEditorFinishCb callback,
+ void *user);
+
+BOOL
+EmbeddedEditor_AdjustWindowRectEx(RECT *rect,
+ unsigned long styleEx,
+ unsigned long style);
+
+
+#define EMBEDDEDEDITOR_WM_FIRST (WM_USER + 10)
+
+#define EMBEDDEDEDITOR_WM_SET_TEXT_COLOR (EMBEDDEDEDITOR_WM_FIRST + 0)
+#define EMBEDDEDEDITOR_SET_TEXT_COLOR(/*HWND*/ _hwnd, /*COLORREF*/ _color)\
+ ((COLORREF)SendMessageW((_hwnd), EMBEDDEDEDITOR_WM_SET_TEXT_COLOR, 0, (LPARAM)(_color)))
+
+#define EMBEDDEDEDITOR_WM_GET_TEXT_COLOR (EMBEDDEDEDITOR_WM_FIRST + 1)
+#define EMBEDDEDEDITOR_GET_TEXT_COLOR(/*HWND*/ _hwnd)\
+ ((COLORREF)SendMessageW((_hwnd), EMBEDDEDEDITOR_WM_GET_TEXT_COLOR, 0, 0L))
+
+#define EMBEDDEDEDITOR_WM_SET_BACK_COLOR (EMBEDDEDEDITOR_WM_FIRST + 2)
+#define EMBEDDEDEDITOR_SET_BACK_COLOR(/*HWND*/ _hwnd, /*COLORREF*/ _color)\
+ ((COLORREF)SendMessageW((_hwnd), EMBEDDEDEDITOR_WM_SET_BACK_COLOR, 0, (LPARAM)(_color)))
+
+#define EMBEDDEDEDITOR_WM_GET_BACK_COLOR (EMBEDDEDEDITOR_WM_FIRST + 3)
+#define EMBEDDEDEDITOR_GET_BACK_COLOR(/*HWND*/ _hwnd)\
+ ((COLORREF)SendMessageW((_hwnd), EMBEDDEDEDITOR_WM_GET_BACK_COLOR, 0, 0L))
+
+#define EMBEDDEDEDITOR_WM_SET_BORDER_COLOR (EMBEDDEDEDITOR_WM_FIRST + 4)
+#define EMBEDDEDEDITOR_SET_BORDER_COLOR(/*HWND*/ _hwnd, /*COLORREF*/ _color)\
+ ((COLORREF)SendMessageW((_hwnd), EMBEDDEDEDITOR_WM_SET_BORDER_COLOR, 0, (LPARAM)(_color)))
+
+#define EMBEDDEDEDITOR_WM_GET_BORDER_COLOR (EMBEDDEDEDITOR_WM_FIRST + 5)
+#define EMBEDDEDEDITOR_GET_BORDER_COLOR(/*HWND*/ _hwnd)\
+ ((COLORREF)SendMessageW((_hwnd), EMBEDDEDEDITOR_WM_GET_BORDER_COLOR, 0, 0L))
+
+#define EMBEDDEDEDITOR_WM_SET_USER_DATA (EMBEDDEDEDITOR_WM_FIRST + 6)
+#define EMBEDDEDEDITOR_SET_USER_DATA(/*HWND*/ _hwnd, /*void* */ _user)\
+ ((void*)SendMessageW((_hwnd), EMBEDDEDEDITOR_WM_SET_USER_DATA, 0, (LPARAM)(_user)))
+
+#define EMBEDDEDEDITOR_WM_GET_USER_DATA (EMBEDDEDEDITOR_WM_FIRST + 7)
+#define EMBEDDEDEDITOR_GET_USER_DATA(/*HWND*/ _hwnd)\
+ ((void*)SendMessageW((_hwnd), EMBEDDEDEDITOR_WM_GET_USER_DATA, 0, 0L))
+
+#define EMBEDDEDEDITOR_WM_SET_ANCHOR_POINT (EMBEDDEDEDITOR_WM_FIRST + 8)
+#define EMBEDDEDEDITOR_SET_ANCHOR_POINT(/*HWND*/ _hwnd, /*long*/ _x, /*long*/ _y)\
+ ((BOOL)SendMessageW((_hwnd), EMBEDDEDEDITOR_WM_SET_ANCHOR_POINT, (WPARAM)(_x), (LPARAM)(_y)))
+
+#define EMBEDDEDEDITOR_WM_GET_ANCHOR_POINT (EMBEDDEDEDITOR_WM_FIRST + 9)
+#define EMBEDDEDEDITOR_GET_ANCHOR_POINT(/*HWND*/ _hwnd, /*long* */ _x, /*long* */ _y)\
+ ((BOOL)SendMessageW((_hwnd), EMBEDDEDEDITOR_WM_GET_ANCHOR_POINT, (WPARAM)(_x), (LPARAM)(_y)))
+
+#define EMBEDDEDEDITOR_WM_SET_MAX_SIZE (EMBEDDEDEDITOR_WM_FIRST + 10)
+#define EMBEDDEDEDITOR_SET_MAX_SIZE(/*HWND*/ _hwnd, /*long*/ _width, /*long*/ _height)\
+ ((BOOL)SendMessageW((_hwnd), EMBEDDEDEDITOR_WM_SET_MAX_SIZE, (WPARAM)(_width), (LPARAM)(_height)))
+
+#define EMBEDDEDEDITOR_WM_GET_MAX_SIZE (EMBEDDEDEDITOR_WM_FIRST + 11)
+#define EMBEDDEDEDITOR_GET_MAX_SIZE(/*HWND*/ _hwnd, /*long* */ _width, /*long* */ _height)\
+ ((BOOL)SendMessageW((_hwnd), EMBEDDEDEDITOR_GET_MAX_SIZE, (WPARAM)(_width), (LPARAM)(_height)))
+
+#define EMBEDDEDEDITOR_WM_END_EDITING (EMBEDDEDEDITOR_WM_FIRST + 12)
+#define EMBEDDEDEDITOR_END_EDITING(/*HWND*/ _hwnd, /*BOOL*/ _cancel)\
+ ((BOOL)SendMessageW((_hwnd), EMBEDDEDEDITOR_WM_END_EDITING, (WPARAM)(_cancel), 0L))
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_EMBEDED_EDITOR_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/eventRelay.cpp b/Src/Plugins/Library/ml_devices/eventRelay.cpp
new file mode 100644
index 00000000..38226887
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/eventRelay.cpp
@@ -0,0 +1,465 @@
+#include "main.h"
+#include "./eventRelay.h"
+#include <vector>
+
+#define EVENT_RELAY_WINDOW_CLASS L"NullsoftEventRelay"
+
+typedef struct EventHandler
+{
+ size_t cookie;
+ DeviceEventCallbacks callbacks;
+ void *user;
+} EventHandler;
+
+typedef std::vector<EventHandler*> EventHandlerList;
+
+typedef struct EventRelay
+{
+ EventHandlerList handlerList;
+ DeviceManagerHandler *managerHandler;
+ DeviceHandler *deviceHandler;
+} EventRelay;
+
+#define EVENTRELAY(_hwnd) ((EventRelay*)(LONGX86)GetWindowLongPtrW((_hwnd), 0))
+#define EVENTRELAY_RET_VOID(_self, _hwnd) {(_self) = EVENTRELAY((_hwnd)); if (NULL == (_self)) return;}
+#define EVENTRELAY_RET_VAL(_self, _hwnd, _error) {(_self) = EVENTRELAY((_hwnd)); if (NULL == (_self)) return (_error);}
+
+
+static LRESULT CALLBACK
+EventRelay_WindowProc(HWND hwnd, unsigned int uMsg, WPARAM wParam, LPARAM lParam);
+
+
+static ATOM
+EventRelay_GetClassAtom(HINSTANCE instance)
+{
+ WNDCLASSEXW klass;
+ ATOM klassAtom;
+
+ klassAtom = (ATOM)GetClassInfoExW(instance, EVENT_RELAY_WINDOW_CLASS, &klass);
+ if (0 != klassAtom)
+ return klassAtom;
+
+ memset(&klass, 0, sizeof(klass));
+ klass.cbSize = sizeof(klass);
+ klass.style = 0;
+ klass.lpfnWndProc = EventRelay_WindowProc;
+ klass.cbClsExtra = 0;
+ klass.cbWndExtra = sizeof(EventRelay*);
+ klass.hInstance = instance;
+ klass.hIcon = NULL;
+ klass.hCursor = NULL;
+ klass.hbrBackground = NULL;
+ klass.lpszMenuName = NULL;
+ klass.lpszClassName = EVENT_RELAY_WINDOW_CLASS;
+ klass.hIconSm = NULL;
+ klassAtom = RegisterClassExW(&klass);
+
+ return klassAtom;
+}
+
+HWND
+EventRelay_CreateWindow()
+{
+ HINSTANCE instance;
+ ATOM klassAtom;
+ HWND hwnd;
+
+ instance = GetModuleHandleW(NULL);
+ klassAtom = EventRelay_GetClassAtom(instance);
+ if (0 == klassAtom)
+ return NULL;
+
+ hwnd = CreateWindowEx(WS_EX_NOACTIVATE | WS_EX_NOPARENTNOTIFY,
+ MAKEINTATOM(klassAtom),
+ NULL,
+ WS_OVERLAPPED,
+ 0, 0, 0, 0,
+ HWND_MESSAGE,
+ NULL,
+ instance,
+ NULL);
+
+ return hwnd;
+}
+
+
+static size_t
+EventRelay_GenerateCookie(EventRelay *self)
+{
+ size_t cookie;
+ EventHandler *handler;
+
+
+ if (NULL == self)
+ return 0;
+
+ cookie = self->handlerList.size() + 1;
+
+ for(;;)
+ {
+ size_t index = self->handlerList.size();
+ while(index--)
+ {
+ handler = self->handlerList[index];
+ if (cookie == handler->cookie)
+ {
+ cookie++;
+ break;
+ }
+ }
+
+ if (((size_t)-1) == index)
+ return cookie;
+ }
+
+ return cookie;
+}
+
+static EventHandler *
+EventRelay_CreateEventHandler(EventRelay *self, DeviceEventCallbacks *callbacks, void *user)
+{
+ EventHandler *handler;
+ size_t cookie;
+
+ if (NULL == self || NULL == callbacks)
+ return NULL;
+
+ cookie = EventRelay_GenerateCookie(self);
+ if (0 == cookie)
+ return NULL;
+
+ handler = (EventHandler*)malloc(sizeof(EventHandler));
+ if (NULL == handler)
+ return NULL;
+
+ handler->user = user;
+ handler->cookie = cookie;
+ handler->callbacks.deviceCb = callbacks->deviceCb;
+ handler->callbacks.typeCb = callbacks->typeCb;
+ handler->callbacks.connectionCb = callbacks->connectionCb;
+ handler->callbacks.commandCb = callbacks->commandCb;
+ handler->callbacks.discoveryCb = callbacks->discoveryCb;
+
+ return handler;
+}
+
+static void
+EventRelay_DestroyEventHandler(EventHandler *handler)
+{
+ if (NULL == handler)
+ return;
+
+ free(handler);
+}
+
+static LRESULT
+EventRelay_OnCreate(HWND hwnd, CREATESTRUCT *createStruct)
+{
+ EventRelay *self;
+ ifc_deviceobjectenum *enumerator;
+ ifc_deviceobject *object;
+ ifc_device *device;
+
+ if (NULL == WASABI_API_DEVICES)
+ return -1;
+
+ self = new EventRelay();
+ if (NULL == self)
+ return -1;
+
+ self->deviceHandler = NULL;
+ self->managerHandler = NULL;
+
+ SetLastError(ERROR_SUCCESS);
+ if (!SetWindowLongPtr(hwnd, 0, (LONGX86)self) && ERROR_SUCCESS != GetLastError())
+ return -1;
+
+ if (FAILED(DeviceHandler::CreateInstance(&self->deviceHandler)))
+ return -1;
+
+ self->deviceHandler->SetRelayWindow(hwnd);
+
+ if (SUCCEEDED(WASABI_API_DEVICES->DeviceEnumerate(&enumerator)))
+ {
+ while(S_OK == enumerator->Next(&object, 1, NULL))
+ {
+ if (SUCCEEDED(object->QueryInterface(IFC_Device, (void**)&device)))
+ {
+ self->deviceHandler->Advise(device);
+ device->Release();
+ }
+ object->Release();
+ }
+ enumerator->Release();
+ }
+
+ if (FAILED(DeviceManagerHandler::CreateInstance(&self->managerHandler)))
+ return -1;
+
+ self->managerHandler->SetRelayWindow(hwnd);
+ if (FAILED(self->managerHandler->Advise(WASABI_API_DEVICES)))
+ return -1;
+
+ return 0;
+}
+
+static void
+EventRelay_OnDestroy(HWND hwnd)
+{
+ EventRelay *self;
+ MSG msg;
+
+ self = EVENTRELAY(hwnd);
+ SetWindowLongPtr(hwnd, 0, 0);
+
+ if (NULL == self)
+ return;
+
+ size_t index = self->handlerList.size();
+ while(index--)
+ {
+ EventHandler *handler = self->handlerList[index];
+ EventRelay_DestroyEventHandler(handler);
+ }
+
+ if (NULL != self->managerHandler)
+ {
+ self->managerHandler->SetRelayWindow(NULL);
+
+ if (NULL != WASABI_API_DEVICES)
+ self->managerHandler->Unadvise(WASABI_API_DEVICES);
+
+ self->managerHandler->Release();
+ }
+
+ if (NULL != self->deviceHandler)
+ {
+ self->deviceHandler->SetRelayWindow(NULL);
+
+ if (NULL != WASABI_API_DEVICES)
+ {
+ ifc_deviceobjectenum *enumerator;
+ ifc_deviceobject *object;
+ ifc_device *device;
+
+ if (SUCCEEDED(WASABI_API_DEVICES->DeviceEnumerate(&enumerator)))
+ {
+ while(S_OK == enumerator->Next(&object, 1, NULL))
+ {
+ if (SUCCEEDED(object->QueryInterface(IFC_Device, (void**)&device)))
+ {
+ self->deviceHandler->Unadvise(device);
+ device->Release();
+ }
+ object->Release();
+ }
+ enumerator->Release();
+ }
+ }
+
+ self->deviceHandler->Release();
+ }
+
+ delete self;
+
+ // finish pumping messages
+ while(FALSE != PeekMessage(&msg, hwnd, EVENTRELAY_WM_FIRST, EVENTRELAY_WM_LAST, PM_REMOVE))
+ {
+ EventRelay_WindowProc(msg.hwnd, msg.message, msg.wParam, msg.lParam);
+ }
+}
+
+static LRESULT
+EventRelay_OnRegisterHandler(HWND hwnd, DeviceEventCallbacks *callbacks, void *user)
+{
+ EventRelay *self;
+ EventHandler *handler;
+
+ EVENTRELAY_RET_VAL(self, hwnd, 0);
+
+ handler = EventRelay_CreateEventHandler(self, callbacks, user);
+ if(NULL == handler)
+ return 0;
+
+ self->handlerList.push_back(handler);
+ return (LRESULT)handler->cookie;
+}
+
+static LRESULT
+EventRelay_OnUnregisterHandler(HWND hwnd, size_t cookie)
+{
+ EventRelay *self;
+
+ EVENTRELAY_RET_VAL(self, hwnd, FALSE);
+
+ size_t index = self->handlerList.size();
+ while(index--)
+ {
+ EventHandler *handler = self->handlerList[index];
+ if (handler->cookie == cookie)
+ {
+ self->handlerList.erase(self->handlerList.begin() + index);
+ EventRelay_DestroyEventHandler(handler);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+EventRelay_OnNotifyDevice(HWND hwnd, ifc_device *device, DeviceEvent eventId)
+{
+ ReplyMessage(0);
+
+ if (NULL != device)
+ {
+ EventRelay *self;
+ self = EVENTRELAY(hwnd);
+
+ if (NULL != self)
+ {
+ switch(eventId)
+ {
+ case Event_DeviceAdded:
+ if (NULL != self->deviceHandler)
+ self->deviceHandler->Advise(device);
+ break;
+
+ case Event_DeviceRemoved:
+ if (NULL != self->deviceHandler)
+ self->deviceHandler->Unadvise(device);
+ break;
+ }
+
+ size_t index = self->handlerList.size();
+ while(index--)
+ {
+ EventHandler *handler = self->handlerList[index];
+ if (NULL != handler->callbacks.deviceCb)
+ handler->callbacks.deviceCb(device, eventId, handler->user);
+ }
+ }
+
+ device->Release();
+ }
+}
+
+static void
+EventRelay_OnNotifyDiscovery(HWND hwnd, api_devicemanager *manager, DeviceDiscoveryEvent eventId)
+{
+ ReplyMessage(0);
+
+ if (NULL != manager)
+ {
+ EventRelay *self;
+ self = EVENTRELAY(hwnd);
+
+ if (NULL != self)
+ {
+ size_t index = self->handlerList.size();
+ while(index--)
+ {
+ EventHandler *handler = self->handlerList[index];
+ if (NULL != handler->callbacks.discoveryCb)
+ handler->callbacks.discoveryCb(manager, eventId, handler->user);
+ }
+ }
+
+ manager->Release();
+ }
+}
+
+static void
+EventRelay_OnNotifyType(HWND hwnd, ifc_devicetype *type, DeviceTypeEvent eventId)
+{
+ ReplyMessage(0);
+
+ if (NULL != type)
+ {
+ EventRelay *self;
+ self = EVENTRELAY(hwnd);
+
+ if (NULL != self)
+ {
+ size_t index = self->handlerList.size();
+ while(index--)
+ {
+ EventHandler *handler = self->handlerList[index];
+ if (NULL != handler->callbacks.typeCb)
+ handler->callbacks.typeCb(type, eventId, handler->user);
+ }
+ }
+
+ type->Release();
+ }
+}
+
+static void
+EventRelay_OnNotifyConnection(HWND hwnd, ifc_deviceconnection *connection, DeviceConnectionEvent eventId)
+{
+ ReplyMessage(0);
+
+ if (NULL != connection)
+ {
+ EventRelay *self;
+ self = EVENTRELAY(hwnd);
+
+ if (NULL != self)
+ {
+ size_t index = self->handlerList.size();
+ while(index--)
+ {
+ EventHandler *handler = self->handlerList[index];
+ if (NULL != handler->callbacks.connectionCb)
+ handler->callbacks.connectionCb(connection, eventId, handler->user);
+ }
+ }
+
+ connection->Release();
+ }
+}
+
+static void
+EventRelay_OnNotifyCommand(HWND hwnd, ifc_devicecommand *command, DeviceCommandEvent eventId)
+{
+ ReplyMessage(0);
+
+ if (NULL != command)
+ {
+ EventRelay *self;
+ self = EVENTRELAY(hwnd);
+
+ if (NULL != self)
+ {
+ size_t index = self->handlerList.size();
+ while(index--)
+ {
+ EventHandler *handler = self->handlerList[index];
+ if (NULL != handler->callbacks.commandCb)
+ handler->callbacks.commandCb(command, eventId, handler->user);
+ }
+ }
+
+ command->Release();
+ }
+}
+
+static LRESULT CALLBACK
+EventRelay_WindowProc(HWND hwnd, unsigned int uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_CREATE: return EventRelay_OnCreate(hwnd, (CREATESTRUCT*)lParam);
+ case WM_DESTROY: EventRelay_OnDestroy(hwnd); return 0;
+
+ case EVENTRELAY_WM_REGISTER_HANDLER: return EventRelay_OnRegisterHandler(hwnd, (DeviceEventCallbacks*)lParam, (void*)wParam);
+ case EVENTRELAY_WM_UNREGISTER_HANDLER: return EventRelay_OnUnregisterHandler(hwnd, (size_t)lParam);
+ case EVENTRELAY_WM_NOTIFY_DEVICE: EventRelay_OnNotifyDevice(hwnd, (ifc_device*)lParam, (DeviceEvent)wParam); return 0;
+ case EVENTRELAY_WM_NOTIFY_DISCOVERY: EventRelay_OnNotifyDiscovery(hwnd, (api_devicemanager*)lParam, (DeviceDiscoveryEvent)wParam); return 0;
+ case EVENTRELAY_WM_NOTIFY_TYPE: EventRelay_OnNotifyType(hwnd, (ifc_devicetype*)lParam, (DeviceTypeEvent)wParam); return 0;
+ case EVENTRELAY_WM_NOTIFY_CONNECTION: EventRelay_OnNotifyConnection(hwnd, (ifc_deviceconnection*)lParam, (DeviceConnectionEvent)wParam); return 0;
+ case EVENTRELAY_WM_NOTIFY_COMMAND: EventRelay_OnNotifyCommand(hwnd, (ifc_devicecommand*)lParam, (DeviceCommandEvent)wParam); return 0;
+ }
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+}
diff --git a/Src/Plugins/Library/ml_devices/eventRelay.h b/Src/Plugins/Library/ml_devices/eventRelay.h
new file mode 100644
index 00000000..572af8a3
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/eventRelay.h
@@ -0,0 +1,116 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_EVENT_RELAY_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_EVENT_RELAY_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+typedef enum DeviceEvent
+{
+ Event_DeviceAdded = 1,
+ Event_DeviceRemoved = 2,
+ Event_DeviceIconChanged = 3,
+ Event_DeviceDisplayNameChanged = 4,
+ Event_DeviceAttached = 5,
+ Event_DeviceDetached = 6,
+ Event_DeviceHidden = 7,
+ Event_DeviceShown = 8,
+ Event_DeviceTotalSpaceChanged = 9,
+ Event_DeviceUsedSpaceChanged = 10,
+ Event_DeviceCommandChanged = 11,
+ Event_DeviceActivityStarted = 12,
+ Event_DeviceActivityFinished = 13,
+ Event_DeviceActivityChanged = 14,
+ Event_DeviceModelChanged = 15,
+ Event_DeviceStatusChanged = 16,
+} DeviceEvent;
+
+typedef enum DeviceTypeEvent
+{
+ Event_TypeRegistered = 1,
+ Event_TypeUnregistered = 2,
+} DeviceTypeEvent;
+
+typedef enum DeviceConnectionEvent
+{
+ Event_ConnectionRegistered = 1,
+ Event_ConnectionUnregistered = 2,
+} DeviceConnectionEvent;
+
+typedef enum DeviceCommandEvent
+{
+ Event_CommandRegistered = 1,
+ Event_CommandUnregistered = 2,
+} DeviceCommandEvent;
+
+typedef enum DeviceDiscoveryEvent
+{
+ Event_DiscoveryStarted = 1,
+ Event_DiscoveryFinished = 2,
+} DeviceDiscoveryEvent;
+
+typedef void (*DeviceEventCb)(ifc_device *device, DeviceEvent eventId, void *user);
+typedef void (*DeviceTypeEventCb)(ifc_devicetype *type, DeviceTypeEvent eventId, void *user);
+typedef void (*DeviceConnectionEventCb)(ifc_deviceconnection *connection, DeviceConnectionEvent eventId, void *user);
+typedef void (*DeviceCommandEventCb)(ifc_devicecommand *command, DeviceCommandEvent eventId, void *user);
+typedef void (*DeviceDiscoveryEventCb)(api_devicemanager *manager, DeviceDiscoveryEvent eventId, void *user);
+
+typedef struct DeviceEventCallbacks
+{
+ DeviceEventCb deviceCb;
+ DeviceTypeEventCb typeCb;
+ DeviceConnectionEventCb connectionCb;
+ DeviceCommandEventCb commandCb;
+ DeviceDiscoveryEventCb discoveryCb;
+} DeviceEventCallbacks;
+
+HWND
+EventRelay_CreateWindow();
+
+
+#define EVENTRELAY_WM_FIRST (WM_USER + 10)
+
+#define EVENTRELAY_WM_REGISTER_HANDLER (EVENTRELAY_WM_FIRST + 0)
+#define EVENTRELAY_REGISTER_HANDLER(/*HWND*/ _hwnd, /*DeviceEventCallbacks*/ _handler, /*void* */ _user)\
+ ((size_t)SendMessageW((_hwnd), EVENTRELAY_WM_REGISTER_HANDLER, (WPARAM)(_user), (LPARAM)(_handler)))
+
+#define EVENTRELAY_WM_UNREGISTER_HANDLER (EVENTRELAY_WM_FIRST + 1)
+#define EVENTRELAY_UNREGISTER_HANDLER(/*HWND*/ _hwnd, /*size_t*/ _handlerCookie)\
+ ((BOOL)SendMessageW((_hwnd), EVENTRELAY_WM_UNREGISTER_HANDLER, 0, (LPARAM)(_handlerCookie)))
+
+
+#define EVENTRELAY_WM_NOTIFY_DEVICE (EVENTRELAY_WM_FIRST + 2)
+#define EVENTRELAY_NOTIFY_DEVICE(/*HWND*/ _hwnd, /*ifc_device* */ _device, /*DeviceEvent*/ _eventId)\
+ { ifc_device *_d = (_device); if (NULL != _d && NULL != (_hwnd)) { _d->AddRef(); \
+ if (FALSE == ((BOOL)PostMessageW((_hwnd), EVENTRELAY_WM_NOTIFY_DEVICE,\
+ (WPARAM)(_eventId), (LPARAM)(_d)))) { _d->Release(); }}}
+
+#define EVENTRELAY_WM_NOTIFY_DISCOVERY (EVENTRELAY_WM_FIRST + 3)
+#define EVENTRELAY_NOTIFY_DISCOVERY(/*HWND*/ _hwnd, /*api_devicemanager* */ _manager, /*DeviceDiscoveryEvent*/ _eventId)\
+ { api_devicemanager *_m = (_manager); if (NULL != _m && NULL != (_hwnd)) { _m->AddRef(); \
+ if (FALSE == ((BOOL)PostMessageW((_hwnd), EVENTRELAY_WM_NOTIFY_DISCOVERY,\
+ (WPARAM)(_eventId), (LPARAM)(_m)))) { _m->Release(); }}}
+
+#define EVENTRELAY_WM_NOTIFY_TYPE (EVENTRELAY_WM_FIRST + 4)
+#define EVENTRELAY_NOTIFY_TYPE(/*HWND*/ _hwnd, /*ifc_devicetype* */ _type, /*DeviceTypeEvent*/ _eventId)\
+ { ifc_devicetype *_t = (_type); if (NULL != _t && NULL != (_hwnd)) { _t->AddRef(); \
+ if (FALSE == ((BOOL)PostMessageW((_hwnd), EVENTRELAY_WM_NOTIFY_TYPE,\
+ (WPARAM)(_eventId), (LPARAM)(_t)))) { _t->Release(); }}}
+
+#define EVENTRELAY_WM_NOTIFY_CONNECTION (EVENTRELAY_WM_FIRST + 5)
+#define EVENTRELAY_NOTIFY_CONNECTION(/*HWND*/ _hwnd, /*ifc_deviceconnection* */ _connection, /*DeviceConnectionEvent*/ _eventId)\
+ { ifc_deviceconnection *_c = (_connection); if (NULL != _c && NULL != (_hwnd)) { _c->AddRef(); \
+ if (FALSE == ((BOOL)PostMessageW((_hwnd), EVENTRELAY_WM_NOTIFY_CONNECTION,\
+ (WPARAM)(_eventId), (LPARAM)(_c)))) { _c->Release(); }}}
+
+#define EVENTRELAY_WM_NOTIFY_COMMAND (EVENTRELAY_WM_FIRST + 6)
+#define EVENTRELAY_NOTIFY_COMMAND(/*HWND*/ _hwnd, /*ifc_devicecommand* */ _command, /*DeviceCommandEvent*/ _eventId)\
+ { ifc_devicecommand *_c = (_command); if (NULL != _c && NULL != (_hwnd)) { _c->AddRef(); \
+ if (FALSE == ((BOOL)PostMessageW((_hwnd), EVENTRELAY_WM_NOTIFY_COMMAND,\
+ (WPARAM)(_eventId), (LPARAM)(_c)))) { _c->Release(); }}}
+
+#define EVENTRELAY_WM_LAST EVENTRELAY_WM_NOTIFY_COMMAND
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_EVENT_RELAY_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/fillRegion.cpp b/Src/Plugins/Library/ml_devices/fillRegion.cpp
new file mode 100644
index 00000000..68f0ee23
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/fillRegion.cpp
@@ -0,0 +1,134 @@
+#include "main.h"
+#include "./fillRegion.h"
+
+static BOOL
+FillRegion_TempRegionFromRect(FillRegion *region, const RECT *rect)
+{
+ if (NULL == region || NULL == rect)
+ return FALSE;
+
+ if (NULL == region->tmp)
+ {
+ region->tmp = CreateRectRgn(rect->left, rect->top, rect->right, rect->bottom);
+ if (NULL == region->tmp)
+ return FALSE;
+ }
+ else
+ {
+ if (FALSE == SetRectRgn(region->tmp, rect->left, rect->top, rect->right, rect->bottom))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL
+FillRegion_Init(FillRegion *region, const RECT *rect)
+{
+ if (NULL == region)
+ return FALSE;
+
+ region->fill = (NULL != rect) ?
+ CreateRectRgn(rect->left, rect->top, rect->right, rect->bottom) :
+ NULL;
+
+ region->tmp = NULL;
+
+ if (NULL == region->fill)
+ return FALSE;
+
+ return TRUE;
+}
+
+void
+FillRegion_Uninit(FillRegion *region)
+{
+ if (NULL != region)
+ {
+ if (NULL != region->fill)
+ {
+ DeleteObject(region->fill);
+ region->fill = NULL;
+ }
+
+ if (NULL != region->tmp)
+ {
+ DeleteObject(region->tmp);
+ region->tmp = NULL;
+ }
+ }
+}
+
+BOOL
+FillRegion_ExcludeRect(FillRegion *region, const RECT *rect)
+{
+ if (NULL == region || NULL == rect)
+ return FALSE;
+
+ return (FALSE != FillRegion_TempRegionFromRect(region, rect) &&
+ ERROR != CombineRgn(region->fill, region->fill, region->tmp, RGN_DIFF));
+}
+
+BOOL
+FillRegion_ExcludeRgn(FillRegion *region, HRGN rgn)
+{
+ if (NULL == region || NULL == rgn)
+ return FALSE;
+
+ return (ERROR != CombineRgn(region->fill, region->fill, rgn, RGN_DIFF));
+}
+
+BOOL
+FillRegion_AppendRect(FillRegion *region, const RECT *rect)
+{
+ if (NULL == region || NULL == rect)
+ return FALSE;
+
+ return (FALSE != FillRegion_TempRegionFromRect(region, rect) &&
+ ERROR != CombineRgn(region->fill, region->fill, region->tmp, RGN_OR));
+}
+
+BOOL
+FillRegion_AppendRgn(FillRegion *region, HRGN rgn)
+{
+ if (NULL == region || NULL == rgn)
+ return FALSE;
+
+ return (ERROR != CombineRgn(region->fill, region->fill, rgn, RGN_OR));
+}
+
+BOOL
+FillRegion_BrushFill(FillRegion *region, HDC hdc, HBRUSH brush)
+{
+ if (NULL == region)
+ return FALSE;
+
+ return FillRgn(hdc, region->fill, brush);
+}
+
+BOOL
+FillRegion_Offset(FillRegion *region, long x, long y)
+{
+ if (NULL == region)
+ return FALSE;
+
+ return (ERROR != OffsetRgn(region->fill, x, y));
+}
+
+BOOL
+FillRegion_SetRect(FillRegion *region, const RECT *rect)
+{
+ if (NULL == region || NULL == rect)
+ return FALSE;
+
+ return SetRectRgn(region->fill, rect->left, rect->top, rect->right, rect->bottom);
+}
+
+BOOL
+FillRegion_SetEmpty(FillRegion *region)
+{
+ if (NULL == region)
+ return FALSE;
+
+ return SetRectRgn(region->fill, 0, 0, 0, 0);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/fillRegion.h b/Src/Plugins/Library/ml_devices/fillRegion.h
new file mode 100644
index 00000000..6337ad62
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/fillRegion.h
@@ -0,0 +1,51 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_FILL_REGION_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_FILL_REGION_HEADER
+
+
+typedef struct FillRegion
+{
+ HRGN fill;
+ HRGN tmp;
+} FillRegion;
+
+BOOL
+FillRegion_Init(FillRegion *region,
+ const RECT *rect);
+
+void
+FillRegion_Uninit(FillRegion *region);
+
+BOOL
+FillRegion_ExcludeRect(FillRegion *region,
+ const RECT *rect);
+
+BOOL
+FillRegion_ExcludeRgn(FillRegion *region,
+ HRGN rgn);
+
+BOOL
+FillRegion_AppendRect(FillRegion *region,
+ const RECT *rect);
+
+BOOL
+FillRegion_AppendRgn(FillRegion *region,
+ HRGN rgn);
+
+BOOL
+FillRegion_BrushFill(FillRegion *region,
+ HDC hdc,
+ HBRUSH brush);
+
+BOOL
+FillRegion_Offset(FillRegion *region,
+ long x,
+ long y);
+
+BOOL
+FillRegion_SetRect(FillRegion *region,
+ const RECT *rect);
+
+BOOL
+FillRegion_SetEmpty(FillRegion *region);
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_FILL_REGION_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/common.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/common.h
new file mode 100644
index 00000000..311ef665
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/common.h
@@ -0,0 +1,76 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_COMMON_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_COMMON_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#ifndef LONGX86
+#ifdef _WIN64
+ #define LONGX86 LONG_PTR
+#else /*_WIN64*/
+ #define LONGX86 LONG
+#endif /*_WIN64*/
+#endif // LONGX86
+
+#define CSTR_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
+
+#ifdef __cplusplus
+ #define SENDMSG(__hwnd, __msgId, __wParam, __lParam) ::SendMessageW((__hwnd), (__msgId), (__wParam), (__lParam))
+#else
+ #define SENDMSG(__hwnd, __msgId, __wParam, __lParam) SendMessageW((__hwnd), (__msgId), (__wParam), (__lParam))
+#endif // __cplusplus
+
+#define SENDMLIPC(__hwndML, __ipcMsgId, __param) SENDMSG((__hwndML), WM_ML_IPC, (WPARAM)(__param), (LPARAM)(__ipcMsgId))
+
+#define SENDWAIPC(__hwndWA, __ipcMsgId, __param) SENDMSG((__hwndWA), WM_WA_IPC, (WPARAM)(__param), (LPARAM)(__ipcMsgId))
+
+#define SENDCMD(__hwnd, __ctrlId, __eventId, __hctrl) (SENDMSG((__hwnd), WM_COMMAND, MAKEWPARAM(__ctrlId, __eventId), (LPARAM)(__hctrl)))
+
+#define DIALOG_RESULT(__hwnd, __result) { SetWindowLongPtrW((__hwnd), DWLP_MSGRESULT, ((LONGX86)(LONG_PTR)(__result))); return TRUE; }
+
+#ifndef GetWindowStyle
+ #define GetWindowStyle(__hwnd) ((UINT)GetWindowLongPtr((__hwnd), GWL_STYLE))
+#endif //GetWindowStyle
+
+#ifndef SetWindowStyle
+ #define SetWindowStyle(__hwnd, __style) (SetWindowLongPtr((__hwnd), GWL_STYLE, (__style)))
+#endif //SetWindowStyle
+
+#ifndef GetWindowStyleEx
+ #define GetWindowStyleEx(__hwnd) ((UINT)GetWindowLongPtr((__hwnd), GWL_EXSTYLE))
+#endif // GetWindowStyleEx
+
+#ifndef SetWindowStyleEx
+ #define SetWindowStyleEx(__hwnd, __style) (SetWindowLongPtr((__hwnd), GWL_EXSTYLE, (__style)))
+#endif //SetWindowStyle
+
+#ifndef RECTWIDTH
+ #define RECTWIDTH(__r) ((__r).right - (__r).left)
+#endif
+
+#ifndef RECTHEIGHT
+ #define RECTHEIGHT(__r) ((__r).bottom - (__r).top)
+#endif
+
+#undef CLAMP
+#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
+
+#ifndef ARRAYSIZE
+ #define ARRAYSIZE(_a) (sizeof(_a)/sizeof((_a)[0]))
+#endif
+
+#ifndef ABS
+ #define ABS(x) (((x) > 0) ? (x) : (-x))
+#endif
+
+#ifndef MIN
+ #define MIN(v1, v2) (((v1) < (v2)) ? (v1) : (v2))
+#endif
+
+#ifndef MAX
+ #define MAX(v1, v2) (((v1) > (v2)) ? (v1) : (v2))
+#endif
+
+
+#endif //_NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_COMMON_HEADER
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/device.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/device.cpp
new file mode 100644
index 00000000..3097e5a1
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/device.cpp
@@ -0,0 +1,871 @@
+#include "main.h"
+#include "./device.h"
+
+#include <strsafe.h>
+
+Device::Device()
+ : ref(1), name(NULL), type(NULL), connection(NULL), displayName(NULL),
+ totalSpace(0), usedSpace(0), attached(FALSE), hidden(FALSE),
+ connected(FALSE), activity(NULL), model(NULL), status(NULL)
+{
+
+ InitializeCriticalSection(&lock);
+
+ if (NULL == WASABI_API_DEVICES ||
+ FAILED(WASABI_API_DEVICES->CreateDeviceEventManager(&eventManager)))
+ {
+ eventManager = NULL;
+ }
+
+ if (NULL == WASABI_API_DEVICES ||
+ FAILED(WASABI_API_DEVICES->CreateIconStore(&iconStore)))
+ {
+ iconStore = NULL;
+ }
+
+ if (NULL == WASABI_API_DEVICES ||
+ FAILED(WASABI_API_DEVICES->CreateSupportedCommandStore(&commands)))
+ {
+ commands = NULL;
+ }
+
+}
+
+Device::~Device()
+{
+ DeviceActivity *activityCopy;
+
+ Lock();
+
+ if (NULL != activity)
+ {
+ activity->SetUser(NULL);
+ activityCopy = activity;
+ activityCopy->AddRef();
+ }
+ else
+ activityCopy = NULL;
+
+ AnsiString_Free(name);
+ AnsiString_Free(type);
+ AnsiString_Free(connection);
+ String_Free(displayName);
+ String_Free(model);
+ String_Free(status);
+
+ if (NULL != commands)
+ commands->Release();
+
+ if (NULL != iconStore)
+ iconStore->Release();
+
+ if (NULL != eventManager)
+ eventManager->Release();
+
+ Unlock();
+
+ if (NULL != activityCopy)
+ {
+
+ activityCopy->Stop();
+ activityCopy->Release();
+ }
+
+ DeleteCriticalSection(&lock);
+}
+
+HRESULT Device::CreateInstance(const char *name, const char *type, const char *connection, Device**instance)
+{
+ Device *self;
+
+ if (NULL == instance)
+ return E_POINTER;
+
+ *instance = NULL;
+
+ self = new Device();
+ if (NULL == self)
+ return E_OUTOFMEMORY;
+
+ self->name = AnsiString_Duplicate(name);
+ self->type = AnsiString_Duplicate(type);
+ self->connection = AnsiString_Duplicate(connection);
+
+ *instance = self;
+ return S_OK;
+}
+
+size_t Device::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+size_t Device::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+int Device::QueryInterface(GUID interface_guid, void **object)
+{
+ if (NULL == object)
+ return E_POINTER;
+
+ if (IsEqualIID(interface_guid, IFC_Device))
+ *object = static_cast<ifc_device*>(this);
+ else
+ {
+ *object = NULL;
+ return E_NOINTERFACE;
+ }
+
+ if (NULL == *object)
+ return E_UNEXPECTED;
+
+ AddRef();
+ return S_OK;
+}
+
+void Device::Lock()
+{
+ EnterCriticalSection(&lock);
+}
+
+void Device::Unlock()
+{
+ LeaveCriticalSection(&lock);
+}
+
+const char *Device::GetName()
+{
+ return name;
+}
+
+const char *Device::GetType()
+{
+ return type;
+}
+
+const char *Device::GetConnection()
+{
+ return connection;
+}
+
+HRESULT Device::GetIcon(wchar_t *buffer, size_t bufferSize, int width, int height)
+{
+ if (NULL == buffer)
+ return E_POINTER;
+
+ if (NULL == iconStore)
+ return E_UNEXPECTED;
+
+ return iconStore->Get(buffer, bufferSize, width, height);
+}
+
+HRESULT Device::GetDisplayName(wchar_t *buffer, size_t bufferSize)
+{
+ HRESULT hr;
+
+ if (NULL == buffer)
+ return E_POINTER;
+
+ Lock();
+
+ if (0 == String_CopyTo(buffer, displayName, bufferSize) &&
+ FALSE == IS_STRING_EMPTY(displayName))
+ {
+ hr = E_FAIL;
+ }
+ else
+ hr = S_OK;
+
+ Unlock();
+
+ return hr;
+}
+
+BOOL Device::GetHidden()
+{
+ return hidden;
+}
+
+HRESULT Device::GetTotalSpace(uint64_t *size)
+{
+ if (NULL == size)
+ return E_POINTER;
+
+ Lock();
+
+ *size = totalSpace;
+
+ Unlock();
+
+ return S_OK;
+}
+
+HRESULT Device::GetUsedSpace(uint64_t *size)
+{
+ if (NULL == size)
+ return E_POINTER;
+
+ Lock();
+
+ *size = usedSpace;
+
+ Unlock();
+
+ return S_OK;
+}
+
+BOOL Device::GetAttached()
+{
+ return attached;
+}
+
+HRESULT Device::Attach(HWND hostWindow)
+{
+ HRESULT hr;
+
+ Lock();
+
+ if (FALSE != attached)
+ hr = S_FALSE;
+ else
+ {
+ attached = TRUE;
+ hr = S_OK;
+ }
+
+ Unlock();
+
+ if (S_OK == hr && NULL != eventManager)
+ eventManager->Notify_AttachmentChanged(this, attached);
+
+ return hr;
+}
+
+HRESULT Device::Detach(HWND hostWindow)
+{
+ HRESULT hr;
+
+ Lock();
+
+ if (FALSE == attached)
+ hr = S_FALSE;
+ else
+ {
+ attached = FALSE;
+ hr = S_OK;
+ }
+
+ Unlock();
+
+ if (S_OK == hr && NULL != eventManager)
+ eventManager->Notify_AttachmentChanged(this, attached);
+
+ return hr;
+}
+
+HRESULT Device::EnumerateCommands(ifc_devicesupportedcommandenum **enumerator, DeviceCommandContext context)
+{
+ if (NULL == commands)
+ return E_UNEXPECTED;
+
+ return commands->Enumerate(enumerator);
+}
+
+HRESULT Device::SendCommand(const char *command, HWND hostWindow, ULONG_PTR param)
+{
+ const wchar_t *commandName;
+ wchar_t message[1024];
+
+ if (NULL == command)
+ return E_POINTER;
+
+ if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, NORM_IGNORECASE, command, -1, "sync", -1))
+ {
+ StartSyncActivity(hostWindow);
+ return S_OK;
+ }
+ else if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, NORM_IGNORECASE, command, -1, "eject", -1))
+ commandName = L"Eject";
+ else if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, NORM_IGNORECASE, command, -1, "detach", -1))
+ {
+ Detach(hostWindow);
+ return S_OK;
+ }
+ else if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, NORM_IGNORECASE, command, -1, "settings", -1))
+ commandName = L"Settings";
+ else
+ return E_NOTIMPL;
+
+ StringCchPrintf(message, ARRAYSIZE(message), L"%s command received", commandName);
+ MessageBox(hostWindow, message, L"Device command test", MB_OK | MB_ICONINFORMATION);
+
+
+ return S_OK;
+}
+
+HRESULT Device::GetCommandFlags(const char *command, DeviceCommandFlags *flags)
+{
+ if (NULL == commands)
+ return E_UNEXPECTED;
+
+ return commands->GetFlags(command, flags);
+}
+
+HRESULT Device::GetActivity(ifc_deviceactivity **activityOut)
+{
+ HRESULT hr;
+
+ if (NULL == activityOut)
+ return E_POINTER;
+
+ Lock();
+
+ *activityOut = activity;
+
+ if (NULL != activity)
+ {
+ activity->AddRef();
+ hr = S_OK;
+ }
+ else
+ hr = S_FALSE;
+
+ Unlock();
+
+ return hr;
+}
+
+HRESULT Device::Advise(ifc_deviceevent *handler)
+{
+ if (NULL == eventManager)
+ return E_UNEXPECTED;
+
+ return eventManager->Advise(handler);
+}
+
+HRESULT Device::Unadvise(ifc_deviceevent *handler)
+{
+ if (NULL == eventManager)
+ return E_UNEXPECTED;
+
+ return eventManager->Unadvise(handler);
+}
+
+HWND Device::CreateView(HWND parentWindow)
+{
+ return DeviceView_CreateWindow(parentWindow, this);
+}
+
+void Device::SetNavigationItem(void *navigationItem)
+{
+}
+
+HRESULT Device::GetModel(wchar_t *buffer, size_t bufferSize)
+{
+ HRESULT hr;
+
+ if (NULL == buffer)
+ return E_POINTER;
+
+ Lock();
+
+ if (0 == String_CopyTo(buffer, model, bufferSize) &&
+ FALSE == IS_STRING_EMPTY(model))
+ {
+ hr = E_FAIL;
+ }
+ else
+ hr = S_OK;
+
+ Unlock();
+
+ return hr;
+}
+HRESULT Device::GetStatus(wchar_t *buffer, size_t bufferSize)
+{
+ HRESULT hr;
+
+ if (NULL == buffer)
+ return E_POINTER;
+
+ Lock();
+
+ if (0 == String_CopyTo(buffer, status, bufferSize) &&
+ FALSE == IS_STRING_EMPTY(status))
+ {
+ hr = E_FAIL;
+ }
+ else
+ hr = S_OK;
+
+ Unlock();
+
+ return hr;
+}
+HRESULT Device::SetConnection(const char *con)
+{
+ Lock();
+
+ AnsiString_Free(connection);
+ connection = AnsiString_Duplicate(con);
+
+ Unlock();
+
+ return S_OK;
+}
+
+HRESULT Device::SetDisplayName(const wchar_t *name)
+{
+ HRESULT hr;
+
+ Lock();
+
+ if (NULL == name && NULL == displayName)
+ hr = S_FALSE;
+ else
+ {
+ if (NULL != displayName &&
+ CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT, 0, name, -1, displayName, -1))
+ {
+ hr = S_FALSE;
+ }
+ else
+ {
+ wchar_t *string;
+
+ string = String_Duplicate(name);
+ if (NULL == string && NULL != name)
+ hr = E_FAIL;
+ else
+ {
+ String_Free(displayName);
+ displayName = string;
+
+ if (NULL != eventManager)
+ eventManager->Notify_DisplayNameChanged(this, displayName);
+
+ hr = S_OK;
+ }
+ }
+ }
+
+ Unlock();
+
+ return hr;
+}
+
+HRESULT Device::SetTotalSpace(uint64_t size)
+{
+ Lock();
+
+ totalSpace = size;
+
+ if (NULL != eventManager)
+ eventManager->Notify_TotalSpaceChanged(this, totalSpace);
+
+ Unlock();
+
+ return S_OK;
+}
+
+HRESULT Device::SetUsedSpace(uint64_t size)
+{
+ Lock();
+
+ usedSpace = size;
+
+ if (NULL != eventManager)
+ eventManager->Notify_UsedSpaceChanged(this, usedSpace);
+
+ Unlock();
+
+ return S_OK;
+}
+
+HRESULT Device::SetModel(const wchar_t *deviceModel)
+{
+ HRESULT hr;
+
+ Lock();
+
+ if (NULL == deviceModel && NULL == model)
+ hr = S_FALSE;
+ else
+ {
+ if (NULL != model &&
+ CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT, 0, deviceModel, -1, model, -1))
+ {
+ hr = S_FALSE;
+ }
+ else
+ {
+ wchar_t *string;
+
+ string = String_Duplicate(deviceModel);
+ if (NULL == string && NULL != deviceModel)
+ hr = E_FAIL;
+ else
+ {
+ String_Free(model);
+ model = string;
+
+ if (NULL != eventManager)
+ eventManager->Notify_ModelChanged(this, model);
+
+ hr = S_OK;
+ }
+ }
+ }
+
+ Unlock();
+
+ return hr;
+}
+
+HRESULT Device::SetStatus(const wchar_t *deviceStatus)
+{
+ HRESULT hr;
+
+ Lock();
+
+ if (NULL == deviceStatus && NULL == status)
+ hr = S_FALSE;
+ else
+ {
+ if (NULL != status &&
+ CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT, 0, deviceStatus, -1, status, -1))
+ {
+ hr = S_FALSE;
+ }
+ else
+ {
+ wchar_t *string;
+
+ string = String_Duplicate(deviceStatus);
+ if (NULL == string && NULL != deviceStatus)
+ hr = E_FAIL;
+ else
+ {
+ String_Free(status);
+ status = string;
+
+ if (NULL != eventManager)
+ eventManager->Notify_StatusChanged(this, status);
+
+ hr = S_OK;
+ }
+ }
+ }
+
+ Unlock();
+
+ return hr;
+}
+
+HRESULT Device::AddIcon(const wchar_t *path, unsigned int width, unsigned int height)
+{
+ HRESULT hr;
+
+ if (NULL == iconStore)
+ return E_UNEXPECTED;
+
+ hr = iconStore->Add(path, width, height, TRUE);
+
+ if (SUCCEEDED(hr))
+ {
+ if (NULL != eventManager)
+ eventManager->Notify_IconChanged(this);
+ }
+
+ return hr;
+}
+
+HRESULT Device::EnumerateIcons(ifc_deviceiconstore::EnumeratorCallback callback, void *user)
+{
+ if (NULL == iconStore)
+ return E_UNEXPECTED;
+
+ return iconStore->Enumerate(callback, user);
+}
+
+
+HRESULT Device::RemoveIcon(unsigned int width, unsigned int height)
+{
+ HRESULT hr;
+
+ if (NULL == iconStore)
+ return E_UNEXPECTED;
+
+ hr = iconStore->Remove(width, height);
+
+ if (SUCCEEDED(hr))
+ {
+ if (NULL != eventManager)
+ eventManager->Notify_IconChanged(this);
+ }
+
+ return hr;
+}
+
+
+HRESULT Device::SetHidden(BOOL hiddenState)
+{
+ HRESULT hr;
+
+ Lock();
+
+ if (hidden == (FALSE != hiddenState))
+ hr = S_FALSE;
+ else
+ {
+ hidden = (FALSE != hiddenState);
+ hr = S_OK;
+ }
+
+ Unlock();
+
+ if (S_OK == hr && NULL != eventManager)
+ eventManager->Notify_VisibilityChanged(this, TRUE);
+
+ return hr;
+}
+
+HRESULT Device::IsConnected()
+{
+ HRESULT hr;
+
+ Lock();
+ hr = (FALSE != connected) ? S_OK : S_FALSE;
+ Unlock();
+
+ return hr;
+}
+
+HRESULT Device::Connect()
+{
+ HRESULT hr;
+
+ Lock();
+
+ if (FALSE != connected)
+ hr = S_FALSE;
+ else
+ {
+ connected = TRUE;
+ hr = S_OK;
+ }
+
+ Unlock();
+
+ return hr;
+}
+
+HRESULT Device::Disconnect()
+{
+ HRESULT hr;
+
+ Lock();
+
+ if (FALSE == connected)
+ hr = S_FALSE;
+ else
+ {
+ connected = FALSE;
+ hr = S_OK;
+ }
+
+ Unlock();
+
+ return hr;
+}
+
+HRESULT Device::CopyTo(Device *target)
+{
+ if (NULL == target)
+ return E_POINTER;
+
+ Lock();
+
+ target->SetDisplayName(displayName);
+
+ if (NULL != target->iconStore)
+ target->iconStore->Release();
+
+ if (NULL == iconStore || FAILED(iconStore->Clone(&target->iconStore)))
+ target->iconStore = NULL;
+
+ target->usedSpace = usedSpace;
+ target->totalSpace = totalSpace;
+ target->hidden = hidden;
+ target->attached = attached;
+ target->connected = connected;
+
+ if (NULL != target->commands)
+ target->commands->Release();
+
+ if (NULL == commands || FAILED(commands->Clone(&target->commands, TRUE)))
+ target->commands = NULL;
+
+ Unlock();
+
+ return S_OK;
+}
+
+HRESULT Device::SetIconBase(const wchar_t *path)
+{
+ if (NULL == iconStore)
+ return E_UNEXPECTED;
+
+ return iconStore->SetBasePath(path);
+}
+
+HRESULT Device::AddCommand(const char *command, DeviceCommandFlags flags)
+{
+ if (NULL == commands)
+ return E_UNEXPECTED;
+
+ return commands->Add(command, flags);
+}
+
+HRESULT Device::RemoveCommand(const char *command)
+{
+ if (NULL == commands)
+ return E_UNEXPECTED;
+
+ return commands->Remove(command);
+
+}
+
+HRESULT Device::SetCommandFlags(const char *command, DeviceCommandFlags mask, DeviceCommandFlags flags)
+{
+ if (NULL == commands)
+ return E_UNEXPECTED;
+
+ return commands->SetFlags(command, mask, flags);
+}
+
+void Device::ActivityStartedCb(DeviceActivity *activity)
+{
+ Device *device;
+
+ if(FAILED(activity->GetUser((void**)&device)) || NULL == device)
+ return;
+
+ if (NULL != device->eventManager)
+ device->eventManager->Notify_ActivityStarted(device, activity);
+}
+
+void Device::ActivityFinishedCb(DeviceActivity *activity)
+{
+ Device *device;
+
+ if(FAILED(activity->GetUser((void**)&device)) || NULL == device)
+ return;
+
+ device->Lock();
+
+ if (activity == device->activity)
+ device->activity = NULL;
+
+
+ device->Unlock();
+
+ if (NULL != device->eventManager)
+ device->eventManager->Notify_ActivityFinished(device, activity);
+
+ activity->Release();
+}
+
+void Device::ActivityProgressCb(DeviceActivity *activity, unsigned int progress, unsigned int duration)
+{
+ Device *device;
+ uint64_t space;
+
+ if(FAILED(activity->GetUser((void**)&device)) || NULL == device)
+ return;
+
+ device->Lock();
+
+ space = device->usedSpace;
+ space++;
+ if (space > device->totalSpace)
+ space = 0;
+
+ device->Unlock();
+
+ device->SetUsedSpace(space);
+
+ if (NULL != device->eventManager)
+ device->eventManager->Notify_ActivityChanged(device, activity);
+
+}
+
+HRESULT Device::StartSyncActivity(HWND hostWindow)
+{
+ HRESULT hr;
+
+ Lock();
+
+ if (NULL != activity)
+ hr = E_PENDING;
+ else
+ {
+ hr = DeviceActivity::CreateInstance(DeviceActivityFlag_Cancelable | DeviceActivityFlag_SupportProgress,
+ ActivityStartedCb, ActivityFinishedCb, ActivityProgressCb,
+ this, &activity);
+
+ if (SUCCEEDED(hr))
+ {
+ activity->SetDisplayName(L"Synchronizing...");
+
+ activity->SetStatus(L"Performing synchronization...");
+ hr = activity->Start(60000, 20);
+ if (FAILED(hr))
+ {
+ activity->Release();
+ activity = NULL;
+ }
+ }
+ }
+ Unlock();
+ return S_OK;
+}
+
+#define CBCLASS Device
+START_DISPATCH;
+CB(ADDREF, AddRef)
+CB(RELEASE, Release)
+CB(QUERYINTERFACE, QueryInterface)
+CB(API_GETNAME, GetName)
+CB(API_GETTYPE, GetType)
+CB(API_GETCONNECTION, GetConnection)
+CB(API_GETICON, GetIcon)
+CB(API_GETDISPLAYNAME, GetDisplayName)
+CB(API_GETHIDDEN, GetHidden)
+CB(API_GETTOTALSPACE, GetTotalSpace)
+CB(API_GETUSEDSPACE, GetUsedSpace)
+CB(API_GETATTACHED, GetAttached)
+CB(API_ATTACH, Attach)
+CB(API_DETACH, Detach)
+CB(API_ENUMERATECOMMANDS, EnumerateCommands)
+CB(API_SENDCOMMAND, SendCommand)
+CB(API_GETCOMMANDFLAGS, GetCommandFlags)
+CB(API_GETACTIVITY, GetActivity)
+CB(API_ADVISE, Advise)
+CB(API_UNADVISE, Unadvise)
+CB(API_CREATEVIEW, CreateView)
+VCB(API_SETNAVIGATIONITEM, SetNavigationItem)
+CB(API_SETDISPLAYNAME, SetDisplayName)
+CB(API_GETMODEL, GetModel)
+CB(API_GETSTATUS, GetStatus)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/device.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/device.h
new file mode 100644
index 00000000..05b5947c
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/device.h
@@ -0,0 +1,114 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+class DeviceActivity;
+
+class Device: public ifc_device
+{
+
+protected:
+ Device();
+ ~Device();
+public:
+ static HRESULT CreateInstance(const char *name,
+ const char *type,
+ const char *connection,
+ Device**instance);
+
+public:
+ /* Dispatchable */
+ size_t AddRef();
+ size_t Release();
+ int QueryInterface(GUID interface_guid, void **object);
+
+ /* ifc_device */
+ const char *GetName();
+ const char *GetType();
+ const char *GetConnection();
+ HRESULT GetIcon(wchar_t *buffer, size_t bufferSize, int width, int height);
+ HRESULT GetDisplayName(wchar_t *buffer, size_t bufferSize);
+ BOOL GetHidden();
+ HRESULT GetTotalSpace(uint64_t *size);
+ HRESULT GetUsedSpace(uint64_t *size);
+ BOOL GetAttached();
+ HRESULT Attach(HWND hostWindow);
+ HRESULT Detach(HWND hostWindow);
+ HRESULT EnumerateCommands(ifc_devicesupportedcommandenum **enumerator, DeviceCommandContext context);
+ HRESULT SendCommand(const char *command, HWND hostWindow, ULONG_PTR param);
+ HRESULT GetCommandFlags(const char *command, DeviceCommandFlags *flags);
+ HRESULT GetActivity(ifc_deviceactivity **activity);
+ HRESULT Advise(ifc_deviceevent *handler);
+ HRESULT Unadvise(ifc_deviceevent *handler);
+
+ HWND CreateView(HWND parentWindow);
+ void SetNavigationItem(void *navigationItem);
+
+ HRESULT GetModel(wchar_t *buffer, size_t bufferSize);
+ HRESULT GetStatus(wchar_t *buffer, size_t bufferSize);
+
+public:
+ HRESULT SetConnection(const char *connection);
+ HRESULT SetDisplayName(const wchar_t *name);
+ HRESULT SetTotalSpace(uint64_t size);
+ HRESULT SetUsedSpace(uint64_t size);
+ HRESULT SetHidden(BOOL hiddenState);
+ HRESULT SetModel(const wchar_t *deviceModel);
+ HRESULT SetStatus(const wchar_t *deviceStatus);
+
+ HRESULT AddIcon(const wchar_t *path, unsigned int width, unsigned int height);
+ HRESULT EnumerateIcons(ifc_deviceiconstore::EnumeratorCallback callback, void *user);
+ HRESULT RemoveIcon(unsigned int width, unsigned int height);
+
+ HRESULT AddCommand(const char *command, DeviceCommandFlags flags);
+ HRESULT RemoveCommand(const char *command);
+ HRESULT SetCommandFlags(const char *command, DeviceCommandFlags mask, DeviceCommandFlags flags);
+
+ HRESULT IsConnected();
+ HRESULT Connect();
+ HRESULT Disconnect();
+
+ HRESULT CopyTo(Device *target);
+ HRESULT SetIconBase(const wchar_t *path);
+
+ HRESULT StartSyncActivity(HWND hostWindow);
+
+
+protected:
+ void Lock();
+ void Unlock();
+
+ static void ActivityStartedCb(DeviceActivity *activity);
+ static void ActivityFinishedCb(DeviceActivity *activity);
+ static void ActivityProgressCb(DeviceActivity *activity, unsigned int progress, unsigned int duration);
+
+
+protected:
+ size_t ref;
+ char *name;
+ char *type;
+ char *connection;
+ wchar_t *displayName;
+ wchar_t *model;
+ wchar_t *status;
+ uint64_t totalSpace;
+ uint64_t usedSpace;
+ BOOL attached;
+ BOOL hidden;
+ BOOL connected;
+ ifc_deviceiconstore *iconStore;
+ ifc_deviceeventmanager *eventManager;
+ ifc_devicesupportedcommandstore *commands;
+ DeviceActivity *activity;
+ CRITICAL_SECTION lock;
+protected:
+ RECVS_DISPATCH;
+};
+
+
+#endif //_NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceActivity.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceActivity.cpp
new file mode 100644
index 00000000..d4d6a093
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceActivity.cpp
@@ -0,0 +1,519 @@
+#include "main.h"
+#include "./deviceActivity.h"
+
+#include <strsafe.h>
+
+typedef struct DeviceActivityThreadParam
+{
+ DeviceActivity *activity;
+ unsigned int duration;
+ unsigned int interval;
+ HANDLE readyEvent;
+}DeviceActivityThreadParam;
+
+DeviceActivity::DeviceActivity(DeviceActivityFlags flags,
+ DeviceActivityCallback startCb, DeviceActivityCallback finishCb,
+ DeviceActivityProgressCallback progressCb, void *user)
+ : ref(1), displayName(NULL), status(NULL), activityThread(FALSE), cancelEvent(NULL), progress(0)
+{
+ InitializeCriticalSection(&lock);
+
+ this->flags = flags;
+ callbackStart = startCb;
+ callbackFinish = finishCb;
+ callbackProgress = progressCb;
+ this->user = user;
+}
+
+DeviceActivity::~DeviceActivity()
+{
+ Stop();
+
+ String_Free(displayName);
+ String_Free(status);
+
+ DeleteCriticalSection(&lock);
+}
+
+
+HRESULT DeviceActivity::CreateInstance(DeviceActivityFlags flags,
+ DeviceActivityCallback startCb, DeviceActivityCallback finishCb,
+ DeviceActivityProgressCallback progressCb, void *user,
+ DeviceActivity **instance)
+{
+ DeviceActivity *self;
+
+ if (NULL == instance)
+ return E_POINTER;
+
+ *instance = NULL;
+
+ self = new DeviceActivity(flags, startCb, finishCb, progressCb, user);
+ if (NULL == self)
+ return E_OUTOFMEMORY;
+
+ *instance = self;
+ return S_OK;
+
+}
+
+size_t DeviceActivity::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+size_t DeviceActivity::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+int DeviceActivity::QueryInterface(GUID interface_guid, void **object)
+{
+ if (NULL == object)
+ return E_POINTER;
+
+ if (IsEqualIID(interface_guid, IFC_DeviceActivity))
+ *object = static_cast<ifc_deviceactivity*>(this);
+ else
+ {
+ *object = NULL;
+ return E_NOINTERFACE;
+ }
+
+ if (NULL == *object)
+ return E_UNEXPECTED;
+
+ AddRef();
+ return S_OK;
+}
+
+void DeviceActivity::Lock()
+{
+ EnterCriticalSection(&lock);
+}
+
+void DeviceActivity::Unlock()
+{
+ LeaveCriticalSection(&lock);
+}
+
+BOOL DeviceActivity::GetActive()
+{
+ BOOL running;
+
+ Lock();
+ running = (NULL != activityThread);
+ Unlock();
+
+ return running;
+}
+
+BOOL DeviceActivity::GetCancelable()
+{
+ BOOL cancelable;
+
+ Lock();
+ cancelable = (0 != (DeviceActivityFlag_Cancelable & flags));
+ Unlock();
+
+ return cancelable;
+}
+
+HRESULT DeviceActivity::GetProgress(unsigned int *percentCompleted)
+{
+
+ if (NULL == percentCompleted)
+ return E_POINTER;
+
+ Lock();
+
+ *percentCompleted = progress;
+
+ Unlock();
+
+ return S_OK;
+}
+
+HRESULT DeviceActivity::GetDisplayName(wchar_t *buffer, size_t bufferMax)
+{
+ HRESULT hr;
+
+ if (NULL == buffer)
+ return E_POINTER;
+
+ Lock();
+
+ if (0 == String_CopyTo(buffer, displayName, bufferMax) &&
+ FALSE == IS_STRING_EMPTY(displayName))
+ {
+ hr = E_FAIL;
+ }
+ else
+ hr = S_OK;
+
+ Unlock();
+
+ return hr;
+}
+
+HRESULT DeviceActivity::GetStatus(wchar_t *buffer, size_t bufferMax)
+{
+ HRESULT hr;
+
+ if (NULL == buffer)
+ return E_POINTER;
+
+ Lock();
+
+ if (0 == String_CopyTo(buffer, status, bufferMax) &&
+ FALSE == IS_STRING_EMPTY(status))
+ {
+ hr = E_FAIL;
+ }
+ else
+ hr = S_OK;
+
+ Unlock();
+
+ return hr;
+}
+
+HRESULT DeviceActivity::Cancel(HWND hostWindow)
+{
+ HRESULT hr;
+
+ Lock();
+
+ if (0 == (DeviceActivityFlag_Cancelable & flags))
+ hr = E_NOTIMPL;
+ else
+ hr = E_FAIL;
+
+ Unlock();
+
+ return hr;
+}
+
+
+HRESULT DeviceActivity::Start(unsigned int duration, unsigned int interval)
+{
+ HRESULT hr;
+
+ Lock();
+
+ if (NULL != activityThread)
+ hr = E_PENDING;
+ else
+ {
+ hr = S_OK;
+
+ if (NULL == cancelEvent)
+ {
+ cancelEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (NULL == cancelEvent)
+ hr = E_FAIL;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ DeviceActivityThreadParam param;
+
+ param.activity = this;
+ param.duration = duration;
+ param.interval = interval;
+ param.readyEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (NULL == param.readyEvent)
+ hr = E_FAIL;
+ else
+ {
+ DWORD threadId;
+
+ activityThread = CreateThread(NULL, 0, DeviceActivity_ActivityThreadStarter,
+ &param, 0, &threadId);
+ if (NULL == activityThread)
+ hr = E_FAIL;
+ else
+ WaitForSingleObject(param.readyEvent, INFINITE);
+
+ CloseHandle(param.readyEvent);
+ }
+ }
+
+ if (FAILED(hr))
+ Stop();
+ }
+
+ Unlock();
+
+ return hr;
+}
+
+HRESULT DeviceActivity::Stop()
+{
+ HRESULT hr;
+ HANDLE threadHandle, eventHandle;
+
+ Lock();
+
+ threadHandle = activityThread;
+ eventHandle = cancelEvent;
+
+ activityThread = NULL;
+ cancelEvent = NULL;
+
+ Unlock();
+
+ if (NULL != threadHandle)
+ {
+ if (NULL != eventHandle)
+ SetEvent(eventHandle);
+
+ WaitForSingleObject(threadHandle, INFINITE);
+ CloseHandle(threadHandle);
+ hr = S_OK;
+ }
+ else
+ hr = S_FALSE;
+
+ if (NULL != eventHandle)
+ CloseHandle(eventHandle);
+
+ return hr;
+}
+
+HRESULT DeviceActivity::SetDisplayName(const wchar_t *name)
+{
+ HRESULT hr;
+
+ Lock();
+
+ if (NULL == name && NULL == displayName)
+ hr = S_FALSE;
+ else
+ {
+ if (NULL != displayName &&
+ CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT, 0, name, -1, displayName, -1))
+ {
+ hr = S_FALSE;
+ }
+ else
+ {
+ wchar_t *string;
+
+ string = String_Duplicate(name);
+ if (NULL == string && NULL != name)
+ hr = E_FAIL;
+ else
+ {
+ String_Free(displayName);
+ displayName = string;
+ hr = S_OK;
+ }
+ }
+ }
+
+ Unlock();
+
+ return hr;
+}
+
+HRESULT DeviceActivity::SetStatus(const wchar_t *newStatus)
+{
+ HRESULT hr;
+
+ if (NULL == newStatus && NULL == status)
+ return S_FALSE;
+
+ Lock();
+
+ if (NULL != status &&
+ CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT, 0, newStatus, -1, status, -1))
+ {
+ hr = S_FALSE;
+ }
+ else
+ {
+ wchar_t *string;
+
+ string = String_Duplicate(newStatus);
+ if (NULL == string && NULL != newStatus)
+ hr = E_FAIL;
+ else
+ {
+ String_Free(status);
+ status = string;
+ hr = S_OK;
+ }
+ }
+
+ Unlock();
+
+ return hr;
+}
+
+HRESULT DeviceActivity::SetUser(void *data)
+{
+ Lock();
+ user = data;
+ Unlock();
+
+ return S_OK;
+
+}
+HRESULT DeviceActivity::GetUser(void **data)
+{
+ if (NULL == data)
+ return E_POINTER;
+
+ Lock();
+
+ *data = user;
+
+ Unlock();
+
+ return S_OK;
+}
+
+DWORD DeviceActivity::ActivityThread(unsigned int duration, unsigned int interval)
+{
+ DWORD waitResult, waitTime;
+ HANDLE cancelEventCopy;
+ unsigned int position;
+
+ if (interval > duration)
+ interval = duration;
+
+
+ position = 0;
+
+ Lock();
+
+ progress = 0;
+
+ if (NULL == cancelEvent ||
+ 0 == DuplicateHandle(GetCurrentProcess(), cancelEvent, GetCurrentProcess(),
+ &cancelEventCopy, 0, FALSE, DUPLICATE_SAME_ACCESS))
+ {
+ cancelEventCopy = NULL;
+ }
+
+ Unlock();
+
+ if(NULL == cancelEventCopy)
+ return -3;
+
+ Lock();
+
+ if (NULL != callbackStart)
+ callbackStart(this);
+
+ if (NULL != callbackProgress)
+ callbackProgress(this, position, duration);
+
+ Unlock();
+
+ for(;;)
+ {
+ waitTime = interval;
+ if ((position + waitTime) > duration)
+ waitTime = duration - position;
+
+ waitResult = WaitForSingleObject(cancelEventCopy, waitTime);
+ if (WAIT_TIMEOUT == waitResult)
+ {
+ position += waitTime;
+
+ Lock();
+
+ if (duration != 0)
+ {
+ progress = 100 * position / duration;
+ if (progress > 100)
+ progress = 100;
+ }
+ else
+ progress = 100;
+
+
+ if (NULL != callbackProgress)
+ callbackProgress(this, position, duration);
+ Unlock();
+
+ if (position >= duration)
+ break;
+ }
+ else
+ break;
+ }
+
+ AddRef();
+
+ Lock();
+
+ if (NULL != activityThread)
+ {
+ CloseHandle(activityThread);
+ activityThread = NULL;
+ }
+
+ if (NULL != cancelEvent)
+ {
+ CloseHandle(cancelEvent);
+ cancelEvent = NULL;
+ }
+
+ if (NULL != callbackFinish)
+ callbackFinish(this);
+
+ Unlock();
+
+ Release();
+
+ return 0;
+}
+
+static DWORD CALLBACK
+DeviceActivity_ActivityThreadStarter(void *user)
+{
+ DeviceActivityThreadParam *param;
+ DeviceActivity *activity;
+ unsigned int duration, interval;
+ DWORD result;
+
+ param = (DeviceActivityThreadParam*)user;
+ activity = param->activity;
+ duration = param->duration;
+ interval = param->interval;
+
+ if (NULL != param->readyEvent)
+ SetEvent(param->readyEvent);
+
+ if (NULL != activity)
+ result = activity->ActivityThread(duration, interval);
+ else
+ result = -2;
+
+ return result;
+}
+
+#define CBCLASS DeviceActivity
+START_DISPATCH;
+CB(ADDREF, AddRef)
+CB(RELEASE, Release)
+CB(QUERYINTERFACE, QueryInterface)
+CB(API_GETACTIVE, GetActive)
+CB(API_GETCANCELABLE, GetCancelable)
+CB(API_GETPROGRESS, GetProgress)
+CB(API_GETDISPLAYNAME, GetDisplayName)
+CB(API_GETSTATUS, GetStatus)
+CB(API_CANCEL, Cancel)
+END_DISPATCH;
+#undef CBCLASS
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceActivity.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceActivity.h
new file mode 100644
index 00000000..f523ac55
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceActivity.h
@@ -0,0 +1,91 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_ACTIVITY_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_ACTIVITY_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+typedef enum DeviceActivityFlags
+{
+ DeviceActivityFlag_Cancelable = (1 << 0),
+ DeviceActivityFlag_SupportProgress = (1 << 0),
+} DeviceActivityFlags;
+DEFINE_ENUM_FLAG_OPERATORS(DeviceActivityFlags);
+
+typedef void (*DeviceActivityCallback)(DeviceActivity * /*activity*/);
+typedef void (*DeviceActivityProgressCallback)(DeviceActivity * /*activity*/, unsigned int /*position*/, unsigned int /*total*/);
+
+
+class DeviceActivity: public ifc_deviceactivity
+{
+
+protected:
+ DeviceActivity(DeviceActivityFlags flags,
+ DeviceActivityCallback startCb,
+ DeviceActivityCallback finishCb,
+ DeviceActivityProgressCallback progressCb,
+ void *user);
+
+ ~DeviceActivity();
+public:
+ static HRESULT CreateInstance(DeviceActivityFlags flags,
+ DeviceActivityCallback startCb,
+ DeviceActivityCallback finishCb,
+ DeviceActivityProgressCallback progressCb,
+ void *user,
+ DeviceActivity **instance);
+
+public:
+ /* Dispatchable */
+ size_t AddRef();
+ size_t Release();
+ int QueryInterface(GUID interface_guid, void **object);
+
+ /* ifc_deviceactivity */
+ BOOL GetActive();
+ BOOL GetCancelable();
+ HRESULT GetProgress(unsigned int *percentCompleted);
+ HRESULT GetDisplayName(wchar_t *buffer, size_t bufferMax);
+ HRESULT GetStatus(wchar_t *buffer, size_t bufferMax);
+ HRESULT Cancel(HWND hostWindow);
+
+public:
+ void Lock();
+ void Unlock();
+
+ HRESULT Start(unsigned int duration, unsigned int interval);
+ HRESULT Stop();
+
+ HRESULT SetDisplayName(const wchar_t *displayName);
+ HRESULT SetStatus(const wchar_t *status);
+
+ HRESULT SetUser(void *data);
+ HRESULT GetUser(void **data);
+
+protected:
+ DWORD ActivityThread(unsigned int duration, unsigned int interval);
+ friend static DWORD CALLBACK DeviceActivity_ActivityThreadStarter(void *param);
+
+protected:
+ size_t ref;
+ DeviceActivityFlags flags;
+ DeviceActivityCallback callbackStart;
+ DeviceActivityCallback callbackFinish;
+ DeviceActivityProgressCallback callbackProgress;
+ void *user;
+ wchar_t *displayName;
+ wchar_t *status;
+ HANDLE activityThread;
+ HANDLE cancelEvent;
+ unsigned int progress;
+ CRITICAL_SECTION lock;
+
+
+protected:
+ RECVS_DISPATCH;
+};
+
+
+#endif //_NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_ACTIVITY_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceCommandNodeParser.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceCommandNodeParser.cpp
new file mode 100644
index 00000000..8c55b6e2
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceCommandNodeParser.cpp
@@ -0,0 +1,75 @@
+#include "main.h"
+#include "./DeviceCommandNodeParser.h"
+#include "../../xml/obj_xml.h"
+
+DeviceCommandNodeParser::DeviceCommandNodeParser()
+ : reader(NULL), test(NULL)
+{
+}
+
+DeviceCommandNodeParser::~DeviceCommandNodeParser()
+{
+ End();
+}
+
+
+BOOL DeviceCommandNodeParser::Begin(obj_xml *xmlReader, TestSuite *testSuite)
+{
+ if (NULL != reader || NULL != test)
+ return FALSE;
+
+ if (NULL == xmlReader || NULL == testSuite)
+ return FALSE;
+
+ reader = xmlReader;
+ reader->AddRef();
+
+ test = testSuite;
+
+ reader->xmlreader_registerCallback(L"testprovider\fcommands\fcommand", this);
+
+ return TRUE;
+}
+
+void DeviceCommandNodeParser::End()
+{
+ if (NULL != reader)
+ {
+ reader->xmlreader_unregisterCallback(this);
+ reader->Release();
+ reader = NULL;
+ }
+
+ if (NULL != test)
+ test = NULL;
+}
+
+
+void DeviceCommandNodeParser::Event_XmlStartElement(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params)
+{
+ elementParser.Begin(reader, params);
+}
+
+void DeviceCommandNodeParser::Event_XmlEndElement(const wchar_t *xmlpath, const wchar_t *xmltag)
+{
+ ifc_devicecommand *result;
+ if (FALSE != elementParser.End(reader, &result))
+ {
+ if (NULL != test)
+ test->AddCommand(result);
+
+ result->Release();
+ }
+}
+
+void DeviceCommandNodeParser::Event_XmlError(int linenum, int errcode, const wchar_t *errstr)
+{
+}
+
+#define CBCLASS DeviceCommandNodeParser
+START_DISPATCH;
+VCB(ONSTARTELEMENT, Event_XmlStartElement)
+VCB(ONENDELEMENT, Event_XmlEndElement)
+VCB(ONERROR, Event_XmlError)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceCommandNodeParser.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceCommandNodeParser.h
new file mode 100644
index 00000000..5b4b4287
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceCommandNodeParser.h
@@ -0,0 +1,39 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_COMMAND_NODE_PARSER_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_COMMAND_NODE_PARSER_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../../xml/ifc_xmlreadercallback.h"
+#include "./deviceCommandParser.h"
+
+class obj_xml;
+
+class DeviceCommandNodeParser : public ifc_xmlreadercallback
+{
+
+public:
+ DeviceCommandNodeParser();
+ ~DeviceCommandNodeParser();
+
+public:
+ BOOL Begin(obj_xml *xmlReader, TestSuite *testSuite);
+ void End();
+
+protected:
+ void Event_XmlStartElement(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params);
+ void Event_XmlEndElement(const wchar_t *xmlpath, const wchar_t *xmltag);
+ void Event_XmlError(int linenum, int errcode, const wchar_t *errstr);
+
+protected:
+ obj_xml *reader;
+ DeviceCommandParser elementParser;
+ TestSuite *test;
+
+protected:
+ RECVS_DISPATCH;
+};
+
+#endif //_NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_COMMAND_NODE_PARSER_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceCommandParser.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceCommandParser.cpp
new file mode 100644
index 00000000..0bfd0365
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceCommandParser.cpp
@@ -0,0 +1,192 @@
+#include "main.h"
+#include "./deviceCommandParser.h"
+
+#include "../../xml/obj_xml.h"
+
+typedef void (*COMMANDTAGCALLBACK)(DeviceCommandParser* /*self*/, ifc_devicecommandeditor* /*editor*/, const wchar_t* /*value*/);
+
+typedef struct COMMANDTAG
+{
+ const wchar_t *name;
+ BOOL multiEntry;
+ COMMANDTAGCALLBACK callback;
+} COMMANDTAG;
+
+static void
+DeviceCommandParser_DisplayNameCb(DeviceCommandParser *self, ifc_devicecommandeditor *editor, const wchar_t *value)
+{
+ editor->SetDisplayName(value);
+}
+
+static void
+DeviceCommandParser_IconCb(DeviceCommandParser *self, ifc_devicecommandeditor *editor, const wchar_t *value)
+{
+ ifc_deviceiconstore *iconStore;
+ if (SUCCEEDED(editor->GetIconStore(&iconStore)))
+ {
+ iconStore->Add(value, self->iconSize.cx, self->iconSize.cy, TRUE);
+ iconStore->Release();
+ }
+}
+
+static void
+DeviceCommandParser_DescirptionCb(DeviceCommandParser *self, ifc_devicecommandeditor *editor, const wchar_t *value)
+{
+ editor->SetDescription(value);
+}
+
+static const COMMANDTAG knownTags[COMMAND_TAG_MAX] =
+{
+ {L"displayName", FALSE, DeviceCommandParser_DisplayNameCb},
+ {L"icon", TRUE, DeviceCommandParser_IconCb},
+ {L"description", FALSE, DeviceCommandParser_DescirptionCb},
+};
+
+DeviceCommandParser::DeviceCommandParser()
+ : editor(NULL)
+{
+}
+
+DeviceCommandParser::~DeviceCommandParser()
+{
+ if (NULL != editor)
+ editor->Release();
+}
+
+BOOL DeviceCommandParser::Begin(obj_xml *reader, ifc_xmlreaderparams *params)
+{
+ const wchar_t *name;
+ ifc_devicecommand *command;
+ char *nameAnsi;
+
+ if (NULL != editor)
+ return FALSE;
+
+ if (NULL == reader || NULL == params)
+ return FALSE;
+
+ name = params->getItemValue(L"name");
+ if (NULL == name)
+ return FALSE;
+
+ nameAnsi = String_ToAnsi(CP_UTF8, 0, name, -1, NULL, NULL);
+ if (NULL == nameAnsi)
+ return FALSE;
+
+ if (NULL != WASABI_API_DEVICES &&
+ SUCCEEDED(WASABI_API_DEVICES->CreateCommand(nameAnsi, &command)))
+ {
+ if(FAILED(command->QueryInterface(IFC_DeviceCommandEditor, (void**)&editor)))
+ editor = NULL;
+
+ command->Release();
+ }
+
+ AnsiString_Free(nameAnsi);
+
+ if (NULL == editor)
+ return FALSE;
+
+ reader->xmlreader_registerCallback(L"testprovider\fcommands\fcommand\fdisplayName", this);
+ reader->xmlreader_registerCallback(L"testprovider\fcommands\fcommand\fdescription", this);
+ reader->xmlreader_registerCallback(L"testprovider\fcommands\fcommand\ficon", this);
+ ZeroMemory(hitList, sizeof(hitList));
+ return TRUE;
+}
+
+BOOL DeviceCommandParser::End(obj_xml *reader, ifc_devicecommand **command)
+{
+ BOOL result;
+
+ if (NULL != reader)
+ reader->xmlreader_unregisterCallback(this);
+
+ if (NULL == command)
+ return FALSE;
+
+ if (NULL == editor)
+ return FALSE;
+
+ if (NULL != command)
+ {
+ if (FAILED(editor->QueryInterface(IFC_DeviceCommand, (void**)command)))
+ result = FALSE;
+ else
+ result = TRUE;
+ }
+ else
+ result = TRUE;
+
+ editor->Release();
+ editor = NULL;
+
+ return result;
+}
+
+void DeviceCommandParser::Event_XmlStartElement(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params)
+{
+ elementString.Clear();
+
+ if (CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, L"icon", -1, xmltag, -1))
+ {
+ const wchar_t *sVal;
+ int iVal;
+
+ sVal = params->getItemValue(L"width");
+ if (NULL == sVal ||
+ FALSE == StrToIntEx(sVal, STIF_DEFAULT, &iVal))
+ {
+ iVal = 0;
+ }
+
+ iconSize.cx = iVal;
+
+ sVal = params->getItemValue(L"height");
+ if (NULL == sVal ||
+ FALSE == StrToIntEx(sVal, STIF_DEFAULT, &iVal))
+ {
+ iVal = 0;
+ }
+
+ iconSize.cy = iVal;
+ }
+}
+
+void DeviceCommandParser::Event_XmlEndElement(const wchar_t *xmlpath, const wchar_t *xmltag)
+{
+ if (NULL == editor)
+ return;
+
+ for (size_t i = 0; i < COMMAND_TAG_MAX; i++)
+ {
+ if (FALSE == hitList[i] &&
+ CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, knownTags[i].name, -1, xmltag, -1))
+ {
+ knownTags[i].callback(this, editor, elementString.Get());
+
+ if (FALSE == knownTags[i].multiEntry)
+ hitList[i] = TRUE;
+
+ break;
+ }
+ }
+}
+
+void DeviceCommandParser::Event_XmlCharData(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *value)
+{
+ elementString.Append(value);
+}
+
+void DeviceCommandParser::Event_XmlError(int linenum, int errcode, const wchar_t *errstr)
+{
+ elementString.Clear();
+}
+
+#define CBCLASS DeviceCommandParser
+START_DISPATCH;
+VCB(ONSTARTELEMENT, Event_XmlStartElement)
+VCB(ONENDELEMENT, Event_XmlEndElement)
+VCB(ONCHARDATA, Event_XmlCharData)
+VCB(ONERROR, Event_XmlError)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceCommandParser.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceCommandParser.h
new file mode 100644
index 00000000..2bd8151e
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceCommandParser.h
@@ -0,0 +1,47 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_COMMAND_PARSER_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_COMMAND_PARSER_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../../xml/ifc_xmlreadercallback.h"
+
+class obj_xml;
+
+#define COMMAND_TAG_MAX 3
+
+class DeviceCommandParser : public ifc_xmlreadercallback
+{
+public:
+ DeviceCommandParser();
+ ~DeviceCommandParser();
+public:
+ BOOL Begin(obj_xml *reader, ifc_xmlreaderparams *params);
+ BOOL End(obj_xml *reader, ifc_devicecommand **command);
+
+protected:
+ void Event_XmlStartElement(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params);
+ void Event_XmlEndElement(const wchar_t *xmlpath, const wchar_t *xmltag);
+ void Event_XmlCharData(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *value);
+ void Event_XmlError(int linenum, int errcode, const wchar_t *errstr);
+
+protected:
+ friend static void DeviceCommandParser_DisplayNameCb(DeviceCommandParser *self, ifc_devicecommandeditor *editor, const wchar_t *value);
+ friend static void DeviceCommandParser_IconCb(DeviceCommandParser *self, ifc_devicecommandeditor *editor, const wchar_t *value);
+ friend static void DeviceCommandParser_DescirptionCb(DeviceCommandParser *self, ifc_devicecommandeditor *editor, const wchar_t *value);
+
+protected:
+ StringBuilder elementString;
+ ifc_devicecommandeditor *editor;
+ BOOL hitList[COMMAND_TAG_MAX];
+ SIZE iconSize;
+
+protected:
+ RECVS_DISPATCH;
+
+};
+
+
+#endif // _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_COMMAND_PARSER_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceConnectionNodeParser.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceConnectionNodeParser.cpp
new file mode 100644
index 00000000..a36f592a
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceConnectionNodeParser.cpp
@@ -0,0 +1,75 @@
+#include "main.h"
+#include "./DeviceConnectionNodeParser.h"
+#include "../../xml/obj_xml.h"
+
+DeviceConnectionNodeParser::DeviceConnectionNodeParser()
+ : reader(NULL), test(NULL)
+{
+}
+
+DeviceConnectionNodeParser::~DeviceConnectionNodeParser()
+{
+ End();
+}
+
+
+BOOL DeviceConnectionNodeParser::Begin(obj_xml *xmlReader, TestSuite *testSuite)
+{
+ if (NULL != reader || NULL != test)
+ return FALSE;
+
+ if (NULL == xmlReader || NULL == testSuite)
+ return FALSE;
+
+ reader = xmlReader;
+ reader->AddRef();
+
+ test = testSuite;
+
+ reader->xmlreader_registerCallback(L"testprovider\fconnections\fconnection", this);
+
+ return TRUE;
+}
+
+void DeviceConnectionNodeParser::End()
+{
+ if (NULL != reader)
+ {
+ reader->xmlreader_unregisterCallback(this);
+ reader->Release();
+ reader = NULL;
+ }
+
+ if (NULL != test)
+ test = NULL;
+}
+
+
+void DeviceConnectionNodeParser::Event_XmlStartElement(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params)
+{
+ elementParser.Begin(reader, params);
+}
+
+void DeviceConnectionNodeParser::Event_XmlEndElement(const wchar_t *xmlpath, const wchar_t *xmltag)
+{
+ ifc_deviceconnection *result;
+ if (FALSE != elementParser.End(reader, &result))
+ {
+ if (NULL != test)
+ test->AddConnection(result);
+
+ result->Release();
+ }
+}
+
+void DeviceConnectionNodeParser::Event_XmlError(int linenum, int errcode, const wchar_t *errstr)
+{
+}
+
+#define CBCLASS DeviceConnectionNodeParser
+START_DISPATCH;
+VCB(ONSTARTELEMENT, Event_XmlStartElement)
+VCB(ONENDELEMENT, Event_XmlEndElement)
+VCB(ONERROR, Event_XmlError)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceConnectionNodeParser.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceConnectionNodeParser.h
new file mode 100644
index 00000000..8c340f5b
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceConnectionNodeParser.h
@@ -0,0 +1,39 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_CONNECTION_NODE_PARSER_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_CONNECTION_NODE_PARSER_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../../xml/ifc_xmlreadercallback.h"
+#include "./deviceConnectionParser.h"
+
+class obj_xml;
+
+class DeviceConnectionNodeParser : public ifc_xmlreadercallback
+{
+
+public:
+ DeviceConnectionNodeParser();
+ ~DeviceConnectionNodeParser();
+
+public:
+ BOOL Begin(obj_xml *xmlReader, TestSuite *testSuite);
+ void End();
+
+protected:
+ void Event_XmlStartElement(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params);
+ void Event_XmlEndElement(const wchar_t *xmlpath, const wchar_t *xmltag);
+ void Event_XmlError(int linenum, int errcode, const wchar_t *errstr);
+
+protected:
+ obj_xml *reader;
+ DeviceConnectionParser elementParser;
+ TestSuite *test;
+
+protected:
+ RECVS_DISPATCH;
+};
+
+#endif //_NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_CONNECTION_NODE_PARSER_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceConnectionParser.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceConnectionParser.cpp
new file mode 100644
index 00000000..daaf0fc4
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceConnectionParser.cpp
@@ -0,0 +1,181 @@
+#include "main.h"
+#include "./deviceConnectionParser.h"
+
+#include "../../xml/obj_xml.h"
+
+typedef void (*CONNECTIONTAGCALLBACK)(DeviceConnectionParser* /*self*/, ifc_deviceconnectioneditor * /*editor*/, const wchar_t* /*value*/);
+
+typedef struct CONNECTIONTAG
+{
+ const wchar_t *name;
+ BOOL multiEntry;
+ CONNECTIONTAGCALLBACK callback;
+} CONNECTIONTAG;
+
+static void
+DeviceConnectionParser_DisplayNameCb(DeviceConnectionParser *self, ifc_deviceconnectioneditor *editor, const wchar_t *value)
+{
+ editor->SetDisplayName(value);
+}
+
+static void
+DeviceConnectionParser_IconCb(DeviceConnectionParser *self, ifc_deviceconnectioneditor *editor, const wchar_t *value)
+{
+ ifc_deviceiconstore *iconStore;
+ if (SUCCEEDED(editor->GetIconStore(&iconStore)))
+ {
+ iconStore->Add(value, self->iconSize.cx, self->iconSize.cy, TRUE);
+ iconStore->Release();
+ }
+}
+
+static const CONNECTIONTAG knownTags[CONNECTION_TAG_MAX] =
+{
+ {L"displayName", FALSE, DeviceConnectionParser_DisplayNameCb},
+ {L"icon", TRUE, DeviceConnectionParser_IconCb},
+};
+
+DeviceConnectionParser::DeviceConnectionParser()
+ : editor(NULL)
+{
+}
+
+DeviceConnectionParser::~DeviceConnectionParser()
+{
+ if (NULL != editor)
+ editor->Release();
+}
+
+BOOL DeviceConnectionParser::Begin(obj_xml *reader, ifc_xmlreaderparams *params)
+{
+ const wchar_t *name;
+ ifc_deviceconnection *connection;
+ char *nameAnsi;
+
+ if (NULL != editor)
+ return FALSE;
+
+ if (NULL == reader || NULL == params)
+ return FALSE;
+
+
+ name = params->getItemValue(L"name");
+ if (NULL == name)
+ return FALSE;
+
+ nameAnsi = String_ToAnsi(CP_UTF8, 0, name, -1, NULL, NULL);
+ if (NULL == nameAnsi)
+ return FALSE;
+
+ if (NULL != WASABI_API_DEVICES &&
+ SUCCEEDED(WASABI_API_DEVICES->CreateConnection(nameAnsi, &connection)))
+ {
+ if(FAILED(connection->QueryInterface(IFC_DeviceConnectionEditor, (void**)&editor)))
+ editor = NULL;
+
+ connection->Release();
+ }
+ AnsiString_Free(nameAnsi);
+
+ if (NULL == editor)
+ return FALSE;
+
+ reader->xmlreader_registerCallback(L"testprovider\fconnections\fconnection\fdisplayName", this);
+ reader->xmlreader_registerCallback(L"testprovider\fconnections\fconnection\ficon", this);
+ ZeroMemory(hitList, sizeof(hitList));
+ return TRUE;
+}
+
+BOOL DeviceConnectionParser::End(obj_xml *reader, ifc_deviceconnection **connection)
+{
+ BOOL result;
+
+ if (NULL != reader)
+ reader->xmlreader_unregisterCallback(this);
+
+ if (NULL == editor)
+ return FALSE;
+
+ if (NULL != connection)
+ {
+ if (FAILED(editor->QueryInterface(IFC_DeviceConnection, (void**)connection)))
+ result = FALSE;
+ else
+ result = TRUE;
+ }
+ else
+ result = TRUE;
+
+ editor->Release();
+ editor = NULL;
+
+ return result;
+}
+
+void DeviceConnectionParser::Event_XmlStartElement(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params)
+{
+ elementString.Clear();
+
+ if (CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, L"icon", -1, xmltag, -1))
+ {
+ const wchar_t *sVal;
+ int iVal;
+
+ sVal = params->getItemValue(L"width");
+ if (NULL == sVal ||
+ FALSE == StrToIntEx(sVal, STIF_DEFAULT, &iVal))
+ {
+ iVal = 0;
+ }
+
+ iconSize.cx = iVal;
+
+ sVal = params->getItemValue(L"height");
+ if (NULL == sVal ||
+ FALSE == StrToIntEx(sVal, STIF_DEFAULT, &iVal))
+ {
+ iVal = 0;
+ }
+
+ iconSize.cy = iVal;
+ }
+}
+
+void DeviceConnectionParser::Event_XmlEndElement(const wchar_t *xmlpath, const wchar_t *xmltag)
+{
+ if (NULL == editor)
+ return;
+
+ for (size_t i = 0; i < CONNECTION_TAG_MAX; i++)
+ {
+ if (FALSE == hitList[i] &&
+ CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, knownTags[i].name, -1, xmltag, -1))
+ {
+ knownTags[i].callback(this, editor, elementString.Get());
+
+ if (FALSE == knownTags[i].multiEntry)
+ hitList[i] = TRUE;
+
+ break;
+ }
+ }
+}
+
+void DeviceConnectionParser::Event_XmlCharData(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *value)
+{
+ elementString.Append(value);
+}
+
+void DeviceConnectionParser::Event_XmlError(int linenum, int errcode, const wchar_t *errstr)
+{
+ elementString.Clear();
+}
+
+#define CBCLASS DeviceConnectionParser
+START_DISPATCH;
+VCB(ONSTARTELEMENT, Event_XmlStartElement)
+VCB(ONENDELEMENT, Event_XmlEndElement)
+VCB(ONCHARDATA, Event_XmlCharData)
+VCB(ONERROR, Event_XmlError)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceConnectionParser.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceConnectionParser.h
new file mode 100644
index 00000000..9b8da274
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceConnectionParser.h
@@ -0,0 +1,46 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_CONNECTION_PARSER_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_CONNECTION_PARSER_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../../xml/ifc_xmlreadercallback.h"
+
+class obj_xml;
+
+#define CONNECTION_TAG_MAX 2
+
+class DeviceConnectionParser : public ifc_xmlreadercallback
+{
+public:
+ DeviceConnectionParser();
+ ~DeviceConnectionParser();
+public:
+ BOOL Begin(obj_xml *reader, ifc_xmlreaderparams *params);
+ BOOL End(obj_xml *reader, ifc_deviceconnection **connection);
+
+protected:
+ void Event_XmlStartElement(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params);
+ void Event_XmlEndElement(const wchar_t *xmlpath, const wchar_t *xmltag);
+ void Event_XmlCharData(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *value);
+ void Event_XmlError(int linenum, int errcode, const wchar_t *errstr);
+
+protected:
+ friend static void DeviceConnectionParser_DisplayNameCb(DeviceConnectionParser *self, ifc_deviceconnectioneditor *editor, const wchar_t *value);
+ friend static void DeviceConnectionParser_IconCb(DeviceConnectionParser *self, ifc_deviceconnectioneditor *editor, const wchar_t *value);
+
+protected:
+ StringBuilder elementString;
+ ifc_deviceconnectioneditor *editor;
+ BOOL hitList[CONNECTION_TAG_MAX];
+ SIZE iconSize;
+
+protected:
+ RECVS_DISPATCH;
+
+};
+
+
+#endif // _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_CONNECTION_PARSER_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceIconEditor.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceIconEditor.cpp
new file mode 100644
index 00000000..41344276
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceIconEditor.cpp
@@ -0,0 +1,178 @@
+#include "main.h"
+#include "./deviceIconEditor.h"
+
+
+#define DEVICEICONEDITOR_PROP L"NullsoftDevicesIconEditorProp"
+
+
+static INT_PTR
+DeviceIconEditor_DialogProc(HWND hwnd, unsigned int uMsg, WPARAM wParam, LPARAM lParam);
+
+INT_PTR
+DeviceIconEditor_Show(HWND parentWindow, DeviceIconInfo *iconInfo)
+{
+ if (NULL == iconInfo)
+ return -1;
+
+ return WASABI_API_DIALOGBOXPARAMW((INT_PTR)IDD_ICON_EDITOR, parentWindow,
+ DeviceIconEditor_DialogProc, (LPARAM)iconInfo);
+
+}
+
+static void
+DeviceIconEditor_UpdateInfo(HWND hwnd)
+{
+ DeviceIconInfo *iconInfo;
+ HWND controlWindow;
+ wchar_t *string;
+
+ iconInfo = (DeviceIconInfo*)GetProp(hwnd, DEVICEICONEDITOR_PROP);
+ if (NULL == iconInfo)
+ return;
+
+ controlWindow = GetDlgItem(hwnd, IDC_EDIT_PATH);
+ if (NULL != controlWindow)
+ {
+ String_Free(iconInfo->path);
+ iconInfo->path = String_FromWindow(controlWindow);
+ }
+
+ controlWindow = GetDlgItem(hwnd, IDC_EDIT_WIDTH);
+ if (NULL != controlWindow)
+ {
+ string = String_FromWindow(controlWindow);
+ if (NULL == string ||
+ FALSE == StrToIntEx(string, STIF_DEFAULT, &iconInfo->width))
+ {
+ iconInfo->width = 0;
+ }
+ String_Free(string);
+ }
+
+ controlWindow = GetDlgItem(hwnd, IDC_EDIT_HEIGHT);
+ if (NULL != controlWindow)
+ {
+ string = String_FromWindow(controlWindow);
+ if (NULL == string ||
+ FALSE == StrToIntEx(string, STIF_DEFAULT, &iconInfo->height))
+ {
+ iconInfo->height = 0;
+ }
+ String_Free(string);
+ }
+}
+
+static INT_PTR
+DeviceIconEditor_OnInitDialog(HWND hwnd, HWND focusWindow, LPARAM param)
+{
+ DeviceIconInfo *iconInfo;
+ HWND controlWindow;
+
+ iconInfo = (DeviceIconInfo*)param;
+ SetProp(hwnd, DEVICEICONEDITOR_PROP, iconInfo);
+
+ if (NULL != iconInfo)
+ {
+ wchar_t buffer[64];
+
+ controlWindow = GetDlgItem(hwnd, IDC_EDIT_PATH);
+ if (NULL != controlWindow)
+ SetWindowText(controlWindow, iconInfo->path);
+
+ controlWindow = GetDlgItem(hwnd, IDC_EDIT_WIDTH);
+ if (NULL != controlWindow)
+ {
+ _itow_s(iconInfo->width, buffer, 10);
+ SetWindowText(controlWindow, buffer);
+ }
+
+ controlWindow = GetDlgItem(hwnd, IDC_EDIT_HEIGHT);
+ if (NULL != controlWindow)
+ {
+ _itow_s(iconInfo->height, buffer, 10);
+ SetWindowText(controlWindow, buffer);
+ }
+
+ }
+ return 0;
+}
+
+static void
+DeviceIconEditor_DisplayFileOpen(HWND hwnd)
+{
+ wchar_t buffer[MAX_PATH * 2];
+ OPENFILENAME ofn;
+ HWND controlWindow;
+
+ buffer[0] = L'\0';
+
+ ZeroMemory(&ofn, sizeof(ofn));
+
+ ofn.lStructSize = sizeof(ofn);
+ ofn.hwndOwner = hwnd;
+ ofn.lpstrFilter = L"Portable Network Graphics\0" L"*.png\0"
+ L"\0";
+ ofn.lpstrFile = buffer;
+ ofn.nMaxFile = ARRAYSIZE(buffer);
+ ofn.lpstrTitle = L"Load Icon";
+ ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
+
+ if (FALSE == GetOpenFileName(&ofn))
+ return;
+
+ controlWindow = GetDlgItem(hwnd, IDC_EDIT_PATH);
+ if (NULL != controlWindow)
+ SetWindowText(controlWindow, buffer);
+}
+
+static void
+DeviceIconEditor_OnDestroy(HWND hwnd)
+{
+ RemoveProp(hwnd, DEVICEICONEDITOR_PROP);
+}
+
+static void
+DeviceIconEditor_OnCommand(HWND hwnd, INT commandId, INT eventId, HWND controlWindow)
+{
+ switch(commandId)
+ {
+ case IDOK:
+ switch(eventId)
+ {
+ case BN_CLICKED:
+ DeviceIconEditor_UpdateInfo(hwnd);
+ EndDialog(hwnd, IDOK);
+ break;
+ }
+ break;
+ case IDCANCEL:
+ switch(eventId)
+ {
+ case BN_CLICKED:
+ EndDialog(hwnd, IDCANCEL);
+ break;
+ }
+ break;
+ case IDC_BUTTON_BROWSE:
+ switch(eventId)
+ {
+ case BN_CLICKED:
+ DeviceIconEditor_DisplayFileOpen(hwnd);
+ break;
+ }
+ break;
+
+ }
+}
+
+static INT_PTR
+DeviceIconEditor_DialogProc(HWND hwnd, unsigned int uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return DeviceIconEditor_OnInitDialog(hwnd, (HWND)wParam, lParam);
+ case WM_DESTROY: DeviceIconEditor_OnDestroy(hwnd); return TRUE;
+ case WM_COMMAND: DeviceIconEditor_OnCommand(hwnd, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); return TRUE;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceIconEditor.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceIconEditor.h
new file mode 100644
index 00000000..c08da395
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceIconEditor.h
@@ -0,0 +1,22 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_ICON_EDITOR_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_ICON_EDITOR_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+typedef struct DeviceIconInfo
+{
+ int width;
+ int height;
+ wchar_t *path;
+} DeviceIconInfo;
+
+INT_PTR
+DeviceIconEditor_Show(HWND parentWindow,
+ DeviceIconInfo *iconInfo);
+
+
+#endif //_NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_ICON_EDITOR_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceNodeParser.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceNodeParser.cpp
new file mode 100644
index 00000000..6582d0c1
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceNodeParser.cpp
@@ -0,0 +1,75 @@
+#include "main.h"
+#include "./DeviceNodeParser.h"
+#include "../../xml/obj_xml.h"
+
+DeviceNodeParser::DeviceNodeParser()
+ : reader(NULL), test(NULL)
+{
+}
+
+DeviceNodeParser::~DeviceNodeParser()
+{
+ End();
+}
+
+
+BOOL DeviceNodeParser::Begin(obj_xml *xmlReader, TestSuite *testSuite)
+{
+ if (NULL != reader || NULL != test)
+ return FALSE;
+
+ if (NULL == xmlReader || NULL == testSuite)
+ return FALSE;
+
+ reader = xmlReader;
+ reader->AddRef();
+
+ test = testSuite;
+
+ reader->xmlreader_registerCallback(L"testprovider\fdevices\fdevice", this);
+
+ return TRUE;
+}
+
+void DeviceNodeParser::End()
+{
+ if (NULL != reader)
+ {
+ reader->xmlreader_unregisterCallback(this);
+ reader->Release();
+ reader = NULL;
+ }
+
+ if (NULL != test)
+ test = NULL;
+}
+
+
+void DeviceNodeParser::Event_XmlStartElement(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params)
+{
+ elementParser.Begin(reader, params);
+}
+
+void DeviceNodeParser::Event_XmlEndElement(const wchar_t *xmlpath, const wchar_t *xmltag)
+{
+ Device *result;
+ if (FALSE != elementParser.End(reader, &result))
+ {
+ if (NULL != test)
+ test->AddDevice(result);
+
+ result->Release();
+ }
+}
+
+void DeviceNodeParser::Event_XmlError(int linenum, int errcode, const wchar_t *errstr)
+{
+}
+
+#define CBCLASS DeviceNodeParser
+START_DISPATCH;
+VCB(ONSTARTELEMENT, Event_XmlStartElement)
+VCB(ONENDELEMENT, Event_XmlEndElement)
+VCB(ONERROR, Event_XmlError)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceNodeParser.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceNodeParser.h
new file mode 100644
index 00000000..667f0e14
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceNodeParser.h
@@ -0,0 +1,39 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_NODE_PARSER_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_NODE_PARSER_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../../xml/ifc_xmlreadercallback.h"
+#include "./deviceParser.h"
+
+class obj_xml;
+
+class DeviceNodeParser : public ifc_xmlreadercallback
+{
+
+public:
+ DeviceNodeParser();
+ ~DeviceNodeParser();
+
+public:
+ BOOL Begin(obj_xml *xmlReader, TestSuite *testSuite);
+ void End();
+
+protected:
+ void Event_XmlStartElement(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params);
+ void Event_XmlEndElement(const wchar_t *xmlpath, const wchar_t *xmltag);
+ void Event_XmlError(int linenum, int errcode, const wchar_t *errstr);
+
+protected:
+ obj_xml *reader;
+ DeviceParser elementParser;
+ TestSuite *test;
+
+protected:
+ RECVS_DISPATCH;
+};
+
+#endif //_NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_NODE_PARSER_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceParser.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceParser.cpp
new file mode 100644
index 00000000..d844641c
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceParser.cpp
@@ -0,0 +1,306 @@
+#include "main.h"
+#include "./deviceParser.h"
+
+#include "../../xml/obj_xml.h"
+
+typedef void (*DEVICETAGCALLBACK)(DeviceParser* /*self*/, Device* /*device*/, const wchar_t* /*value*/);
+
+typedef struct DEVICETAG
+{
+ const wchar_t *name;
+ BOOL multiEntry;
+ DEVICETAGCALLBACK callback;
+} DEVICETAG;
+
+
+static void
+DeviceParser_ConnectionCb(DeviceParser *self, Device *device, const wchar_t *value)
+{
+ char *connectionAnsi;
+
+ connectionAnsi = (NULL != value) ?
+ String_ToAnsi(CP_UTF8, 0, value, -1, NULL, NULL) :
+ NULL;
+
+ device->SetConnection(connectionAnsi);
+ AnsiString_Free(connectionAnsi);
+}
+
+
+static void
+DeviceParser_DisplayNameCb(DeviceParser *self, Device *device, const wchar_t *value)
+{
+ device->SetDisplayName(value);
+}
+
+static void
+DeviceParser_IconCb(DeviceParser *self, Device *device, const wchar_t *value)
+{
+ device->AddIcon(value, self->iconSize.cx, self->iconSize.cy);
+}
+
+static void
+DeviceParser_TotalSpaceCb(DeviceParser *self, Device *device, const wchar_t *value)
+{
+ size_t size;
+
+ if (NULL == value)
+ size = -1;
+ else
+ {
+ LONGLONG lval;
+ if (FALSE == StrToInt64Ex(value, STIF_DEFAULT, &lval))
+ return;
+
+ size = (size_t)lval;
+ }
+ device->SetTotalSpace(size);
+}
+
+static void
+DeviceParser_UsedSpaceCb(DeviceParser *self, Device *device, const wchar_t *value)
+{
+ size_t size;
+
+ if (NULL == value)
+ size = -1;
+ else
+ {
+ LONGLONG lval;
+ if (FALSE == StrToInt64Ex(value, STIF_DEFAULT, &lval))
+ return;
+
+ size = (size_t)lval;
+ }
+ device->SetUsedSpace(size);
+}
+
+static void
+DeviceParser_HiddenCb(DeviceParser *self, Device *device, const wchar_t *value)
+{
+ int hidden;
+ if (FALSE != StrToIntEx(value, STIF_DEFAULT, &hidden))
+ device->SetHidden(0 != hidden);
+}
+
+static void
+DeviceParser_CommandCb(DeviceParser *self, Device *device, const wchar_t *value)
+{
+ char *command;
+
+ if (FALSE != IS_STRING_EMPTY(value))
+ return;
+ command = String_ToAnsi(CP_UTF8, 0, value, -1, NULL, NULL);
+ if (NULL != command)
+ {
+ device->AddCommand(command, self->commandFlags);
+ AnsiString_Free(command);
+ }
+}
+
+
+static void
+DeviceParser_ModelCb(DeviceParser *self, Device *device, const wchar_t *value)
+{
+ device->SetModel(value);
+}
+
+static void
+DeviceParser_StatusCb(DeviceParser *self, Device *device, const wchar_t *value)
+{
+ device->SetStatus(value);
+}
+
+static const DEVICETAG knownTags[DEVICE_TAG_MAX] =
+{
+ {L"connection", FALSE, DeviceParser_ConnectionCb},
+ {L"displayName", FALSE, DeviceParser_DisplayNameCb},
+ {L"icon", TRUE, DeviceParser_IconCb},
+ {L"totalSpace", FALSE, DeviceParser_TotalSpaceCb},
+ {L"usedSpace", FALSE, DeviceParser_UsedSpaceCb},
+ {L"hidden", FALSE, DeviceParser_HiddenCb},
+ {L"command", TRUE, DeviceParser_CommandCb},
+ {L"model", FALSE, DeviceParser_ModelCb},
+ {L"status", FALSE, DeviceParser_StatusCb},
+
+};
+
+DeviceParser::DeviceParser()
+ : device(NULL)
+{
+}
+
+DeviceParser::~DeviceParser()
+{
+ if (NULL != device)
+ device->Release();
+}
+
+BOOL DeviceParser::Begin(obj_xml *reader, ifc_xmlreaderparams *params)
+{
+ const wchar_t *value;
+ char *nameAnsi, *typeAnsi;
+ if (NULL != device)
+ return FALSE;
+
+ if (NULL == reader || NULL == params)
+ return FALSE;
+
+ value = params->getItemValue(L"name");
+ nameAnsi = (NULL != value) ? String_ToAnsi(CP_UTF8, 0, value, -1, NULL, NULL) : NULL;
+
+ value = params->getItemValue(L"type");
+ typeAnsi = (NULL != value) ? String_ToAnsi(CP_UTF8, 0, value, -1, NULL, NULL) : NULL;
+
+ if (NULL == nameAnsi ||
+ NULL == typeAnsi ||
+ FAILED(Device::CreateInstance(nameAnsi, typeAnsi, NULL, &device)))
+ {
+ device = NULL;
+ }
+
+ AnsiString_Free(nameAnsi);
+ AnsiString_Free(typeAnsi);
+
+ if (NULL == device)
+ return FALSE;
+
+ reader->xmlreader_registerCallback(L"testprovider\fdevices\fdevice\fconnection", this);
+ reader->xmlreader_registerCallback(L"testprovider\fdevices\fdevice\fdisplayName", this);
+ reader->xmlreader_registerCallback(L"testprovider\fdevices\fdevice\ficon", this);
+ reader->xmlreader_registerCallback(L"testprovider\fdevices\fdevice\ftotalSpace", this);
+ reader->xmlreader_registerCallback(L"testprovider\fdevices\fdevice\fusedSpace", this);
+ reader->xmlreader_registerCallback(L"testprovider\fdevices\fdevice\fhidden", this);
+ reader->xmlreader_registerCallback(L"testprovider\fdevices\fdevice\fcommand", this);
+ reader->xmlreader_registerCallback(L"testprovider\fdevices\fdevice\fmodel", this);
+ reader->xmlreader_registerCallback(L"testprovider\fdevices\fdevice\fstatus", this);
+ ZeroMemory(hitList, sizeof(hitList));
+ return TRUE;
+}
+
+BOOL DeviceParser::End(obj_xml *reader, Device **result)
+{
+ if (NULL != reader)
+ reader->xmlreader_unregisterCallback(this);
+
+ if (NULL == device)
+ return FALSE;
+
+ if (NULL != result)
+ {
+ *result = device;
+ device->AddRef();
+ }
+
+ device->Release();
+ device = NULL;
+
+ return TRUE;
+}
+
+void DeviceParser::Event_XmlStartElement(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params)
+{
+ elementString.Clear();
+ if (CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, L"icon", -1, xmltag, -1))
+ {
+ const wchar_t *sVal;
+ int iVal;
+
+ sVal = params->getItemValue(L"width");
+ if (NULL == sVal ||
+ FALSE == StrToIntEx(sVal, STIF_DEFAULT, &iVal))
+ {
+ iVal = 0;
+ }
+
+ iconSize.cx = iVal;
+
+ sVal = params->getItemValue(L"height");
+ if (NULL == sVal ||
+ FALSE == StrToIntEx(sVal, STIF_DEFAULT, &iVal))
+ {
+ iVal = 0;
+ }
+
+ iconSize.cy = iVal;
+ }
+ else if (CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, L"command", -1, xmltag, -1))
+ {
+ const wchar_t *sVal;
+ int iVal;
+
+ commandFlags = DeviceCommandFlag_None;
+
+ sVal = params->getItemValue(L"primary");
+ if (FALSE == IS_STRING_EMPTY(sVal) &&
+ FALSE != StrToIntEx(sVal, STIF_DEFAULT, &iVal) &&
+ 0 != iVal)
+ {
+ commandFlags |= DeviceCommandFlag_Primary;
+ }
+
+ sVal = params->getItemValue(L"group");
+ if (FALSE == IS_STRING_EMPTY(sVal) &&
+ FALSE != StrToIntEx(sVal, STIF_DEFAULT, &iVal) &&
+ 0 != iVal)
+ {
+ commandFlags |= DeviceCommandFlag_Group;
+ }
+
+ sVal = params->getItemValue(L"disabled");
+ if (FALSE == IS_STRING_EMPTY(sVal) &&
+ FALSE != StrToIntEx(sVal, STIF_DEFAULT, &iVal) &&
+ 0 != iVal)
+ {
+ commandFlags |= DeviceCommandFlag_Disabled;
+ }
+
+ sVal = params->getItemValue(L"hidden");
+ if (FALSE == IS_STRING_EMPTY(sVal) &&
+ FALSE != StrToIntEx(sVal, STIF_DEFAULT, &iVal) &&
+ 0 != iVal)
+ {
+ commandFlags |= DeviceCommandFlag_Hidden;
+ }
+ }
+
+}
+
+void DeviceParser::Event_XmlEndElement(const wchar_t *xmlpath, const wchar_t *xmltag)
+{
+ if (NULL == device)
+ return;
+
+ for (size_t i = 0; i < DEVICE_TAG_MAX; i++)
+ {
+ if (FALSE == hitList[i] &&
+ CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, knownTags[i].name, -1, xmltag, -1))
+ {
+ knownTags[i].callback(this, device, elementString.Get());
+
+ if (FALSE == knownTags[i].multiEntry)
+ hitList[i] = TRUE;
+
+ break;
+ }
+ }
+}
+
+void DeviceParser::Event_XmlCharData(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *value)
+{
+ elementString.Append(value);
+}
+
+void DeviceParser::Event_XmlError(int linenum, int errcode, const wchar_t *errstr)
+{
+ elementString.Clear();
+}
+
+#define CBCLASS DeviceParser
+START_DISPATCH;
+VCB(ONSTARTELEMENT, Event_XmlStartElement)
+VCB(ONENDELEMENT, Event_XmlEndElement)
+VCB(ONCHARDATA, Event_XmlCharData)
+VCB(ONERROR, Event_XmlError)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceParser.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceParser.h
new file mode 100644
index 00000000..26164690
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceParser.h
@@ -0,0 +1,54 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_PARSER_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_PARSER_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../../xml/ifc_xmlreadercallback.h"
+#include "./device.h"
+
+class obj_xml;
+
+#define DEVICE_TAG_MAX 9
+
+class DeviceParser : public ifc_xmlreadercallback
+{
+public:
+ DeviceParser();
+ ~DeviceParser();
+public:
+ BOOL Begin(obj_xml *reader, ifc_xmlreaderparams *params);
+ BOOL End(obj_xml *reader, Device **result);
+
+protected:
+ void Event_XmlStartElement(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params);
+ void Event_XmlEndElement(const wchar_t *xmlpath, const wchar_t *xmltag);
+ void Event_XmlCharData(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *value);
+ void Event_XmlError(int linenum, int errcode, const wchar_t *errstr);
+protected:
+ friend static void DeviceParser_IconCb(DeviceParser *self, Device *device, const wchar_t *value);
+ friend static void DeviceParser_ConnectionCb(DeviceParser *self, Device *device, const wchar_t *value);
+ friend static void DeviceParser_DisplayNameCb(DeviceParser *self, Device *device, const wchar_t *value);
+ friend static void DeviceParser_TotalSpaceCb(DeviceParser *self, Device *device, const wchar_t *value);
+ friend static void DeviceParser_UsedSpaceCb(DeviceParser *self, Device *device, const wchar_t *value);
+ friend static void DeviceParser_HiddenCb(DeviceParser *self, Device *device, const wchar_t *value);
+ friend static void DeviceParser_CommandCb(DeviceParser *self, Device *device, const wchar_t *value);
+ friend static void DeviceParser_ModelCb(DeviceParser *self, Device *device, const wchar_t *value);
+ friend static void DeviceParser_StatusCb(DeviceParser *self, Device *device, const wchar_t *value);
+
+protected:
+ StringBuilder elementString;
+ Device *device;
+ BOOL hitList[DEVICE_TAG_MAX];
+ SIZE iconSize;
+ DeviceCommandFlags commandFlags;
+
+protected:
+ RECVS_DISPATCH;
+
+};
+
+
+#endif // _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_PARSER_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceTypeNodeParser.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceTypeNodeParser.cpp
new file mode 100644
index 00000000..11f598ea
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceTypeNodeParser.cpp
@@ -0,0 +1,75 @@
+#include "main.h"
+#include "./DeviceTypeNodeParser.h"
+#include "../../xml/obj_xml.h"
+
+DeviceTypeNodeParser::DeviceTypeNodeParser()
+ : reader(NULL), test(NULL)
+{
+}
+
+DeviceTypeNodeParser::~DeviceTypeNodeParser()
+{
+ End();
+}
+
+
+BOOL DeviceTypeNodeParser::Begin(obj_xml *xmlReader, TestSuite *testSuite)
+{
+ if (NULL != reader || NULL != test)
+ return FALSE;
+
+ if (NULL == xmlReader || NULL == testSuite)
+ return FALSE;
+
+ reader = xmlReader;
+ reader->AddRef();
+
+ test = testSuite;
+
+ reader->xmlreader_registerCallback(L"testprovider\ftypes\ftype", this);
+
+ return TRUE;
+}
+
+void DeviceTypeNodeParser::End()
+{
+ if (NULL != reader)
+ {
+ reader->xmlreader_unregisterCallback(this);
+ reader->Release();
+ reader = NULL;
+ }
+
+ if (NULL != test)
+ test = NULL;
+}
+
+
+void DeviceTypeNodeParser::Event_XmlStartElement(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params)
+{
+ elementParser.Begin(reader, params);
+}
+
+void DeviceTypeNodeParser::Event_XmlEndElement(const wchar_t *xmlpath, const wchar_t *xmltag)
+{
+ ifc_devicetype *result;
+ if (FALSE != elementParser.End(reader, &result))
+ {
+ if (NULL != test)
+ test->AddType(result);
+
+ result->Release();
+ }
+}
+
+void DeviceTypeNodeParser::Event_XmlError(int linenum, int errcode, const wchar_t *errstr)
+{
+}
+
+#define CBCLASS DeviceTypeNodeParser
+START_DISPATCH;
+VCB(ONSTARTELEMENT, Event_XmlStartElement)
+VCB(ONENDELEMENT, Event_XmlEndElement)
+VCB(ONERROR, Event_XmlError)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceTypeNodeParser.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceTypeNodeParser.h
new file mode 100644
index 00000000..f464c213
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceTypeNodeParser.h
@@ -0,0 +1,39 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_TYPE_NODE_PARSER_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_TYPE_NODE_PARSER_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../../xml/ifc_xmlreadercallback.h"
+#include "./deviceTypeParser.h"
+
+class obj_xml;
+
+class DeviceTypeNodeParser : public ifc_xmlreadercallback
+{
+
+public:
+ DeviceTypeNodeParser();
+ ~DeviceTypeNodeParser();
+
+public:
+ BOOL Begin(obj_xml *xmlReader, TestSuite *testSuite);
+ void End();
+
+protected:
+ void Event_XmlStartElement(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params);
+ void Event_XmlEndElement(const wchar_t *xmlpath, const wchar_t *xmltag);
+ void Event_XmlError(int linenum, int errcode, const wchar_t *errstr);
+
+protected:
+ obj_xml *reader;
+ DeviceTypeParser elementParser;
+ TestSuite *test;
+
+protected:
+ RECVS_DISPATCH;
+};
+
+#endif //_NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_TYPE_NODE_PARSER_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceTypeParser.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceTypeParser.cpp
new file mode 100644
index 00000000..3dba9447
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceTypeParser.cpp
@@ -0,0 +1,182 @@
+#include "main.h"
+#include "./deviceTypeParser.h"
+
+#include "../../xml/obj_xml.h"
+
+typedef void (*TYPETAGCALLBACK)(DeviceTypeParser* /*self*/, ifc_devicetypeeditor* /*editor*/, const wchar_t* /*value*/);
+
+typedef struct TYPETAG
+{
+ const wchar_t *name;
+ BOOL multiEntry;
+ TYPETAGCALLBACK callback;
+} TYPETAG;
+
+static void
+DeviceTypeParser_DisplayNameCb(DeviceTypeParser *self, ifc_devicetypeeditor *editor, const wchar_t *value)
+{
+ editor->SetDisplayName(value);
+}
+
+static void
+DeviceTypeParser_IconCb(DeviceTypeParser *self, ifc_devicetypeeditor *editor, const wchar_t *value)
+{
+ ifc_deviceiconstore *iconStore;
+ if (SUCCEEDED(editor->GetIconStore(&iconStore)))
+ {
+ iconStore->Add(value, self->iconSize.cx, self->iconSize.cy, TRUE);
+ iconStore->Release();
+ }
+}
+
+static const TYPETAG knownTags[TYPE_TAG_MAX] =
+{
+ {L"displayName", FALSE, DeviceTypeParser_DisplayNameCb},
+ {L"icon", TRUE, DeviceTypeParser_IconCb},
+};
+
+DeviceTypeParser::DeviceTypeParser()
+ : editor(NULL)
+{
+}
+
+DeviceTypeParser::~DeviceTypeParser()
+{
+ if (NULL != editor)
+ editor->Release();
+}
+
+BOOL DeviceTypeParser::Begin(obj_xml *reader, ifc_xmlreaderparams *params)
+{
+ const wchar_t *name;
+ ifc_devicetype *type;
+ char *nameAnsi;
+
+ if (NULL != editor)
+ return FALSE;
+
+ if (NULL == reader || NULL == params)
+ return FALSE;
+
+
+ name = params->getItemValue(L"name");
+ if (NULL == name)
+ return FALSE;
+
+ nameAnsi = String_ToAnsi(CP_UTF8, 0, name, -1, NULL, NULL);
+ if (NULL == nameAnsi)
+ return FALSE;
+
+ if (NULL != WASABI_API_DEVICES &&
+ SUCCEEDED(WASABI_API_DEVICES->CreateType(nameAnsi, &type)))
+ {
+ if(FAILED(type->QueryInterface(IFC_DeviceTypeEditor, (void**)&editor)))
+ editor = NULL;
+
+ type->Release();
+ }
+
+ AnsiString_Free(nameAnsi);
+
+ if (NULL == editor)
+ return FALSE;
+
+ reader->xmlreader_registerCallback(L"testprovider\ftypes\ftype\fdisplayName", this);
+ reader->xmlreader_registerCallback(L"testprovider\ftypes\ftype\ficon", this);
+ ZeroMemory(hitList, sizeof(hitList));
+ return TRUE;
+}
+
+BOOL DeviceTypeParser::End(obj_xml *reader, ifc_devicetype **deviceType)
+{
+ BOOL result;
+
+ if (NULL != reader)
+ reader->xmlreader_unregisterCallback(this);
+
+ if (NULL == editor)
+ return FALSE;
+
+ if (NULL != deviceType)
+ {
+ if (FAILED(editor->QueryInterface(IFC_DeviceType, (void**)deviceType)))
+ result = FALSE;
+ else
+ result = TRUE;
+ }
+ else
+ result = TRUE;
+
+ editor->Release();
+ editor = NULL;
+
+ return result;
+}
+
+void DeviceTypeParser::Event_XmlStartElement(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params)
+{
+ elementString.Clear();
+
+ if (CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, L"icon", -1, xmltag, -1))
+ {
+ const wchar_t *sVal;
+ int iVal;
+
+ sVal = params->getItemValue(L"width");
+ if (NULL == sVal ||
+ FALSE == StrToIntEx(sVal, STIF_DEFAULT, &iVal))
+ {
+ iVal = 0;
+ }
+
+ iconSize.cx = iVal;
+
+ sVal = params->getItemValue(L"height");
+ if (NULL == sVal ||
+ FALSE == StrToIntEx(sVal, STIF_DEFAULT, &iVal))
+ {
+ iVal = 0;
+ }
+
+ iconSize.cy = iVal;
+ }
+}
+
+void DeviceTypeParser::Event_XmlEndElement(const wchar_t *xmlpath, const wchar_t *xmltag)
+{
+ if (NULL == editor)
+ return;
+
+ for (size_t i = 0; i < TYPE_TAG_MAX; i++)
+ {
+ if (FALSE == hitList[i] &&
+ CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, knownTags[i].name, -1, xmltag, -1))
+ {
+ knownTags[i].callback(this, editor, elementString.Get());
+
+ if (FALSE == knownTags[i].multiEntry)
+ hitList[i] = TRUE;
+
+ break;
+ }
+ }
+}
+
+void DeviceTypeParser::Event_XmlCharData(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *value)
+{
+ elementString.Append(value);
+}
+
+void DeviceTypeParser::Event_XmlError(int linenum, int errcode, const wchar_t *errstr)
+{
+ elementString.Clear();
+}
+
+#define CBCLASS DeviceTypeParser
+START_DISPATCH;
+VCB(ONSTARTELEMENT, Event_XmlStartElement)
+VCB(ONENDELEMENT, Event_XmlEndElement)
+VCB(ONCHARDATA, Event_XmlCharData)
+VCB(ONERROR, Event_XmlError)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceTypeParser.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceTypeParser.h
new file mode 100644
index 00000000..7584eb54
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceTypeParser.h
@@ -0,0 +1,46 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_TYPE_PARSER_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_TYPE_PARSER_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../../xml/ifc_xmlreadercallback.h"
+
+class obj_xml;
+
+#define TYPE_TAG_MAX 2
+
+class DeviceTypeParser : public ifc_xmlreadercallback
+{
+public:
+ DeviceTypeParser();
+ ~DeviceTypeParser();
+public:
+ BOOL Begin(obj_xml *reader, ifc_xmlreaderparams *params);
+ BOOL End(obj_xml *reader, ifc_devicetype **deviceType);
+
+protected:
+ void Event_XmlStartElement(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params);
+ void Event_XmlEndElement(const wchar_t *xmlpath, const wchar_t *xmltag);
+ void Event_XmlCharData(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *value);
+ void Event_XmlError(int linenum, int errcode, const wchar_t *errstr);
+
+protected:
+ friend static void DeviceTypeParser_DisplayNameCb(DeviceTypeParser *self, ifc_devicetypeeditor *editor, const wchar_t *value);
+ friend static void DeviceTypeParser_IconCb(DeviceTypeParser *self, ifc_devicetypeeditor *editor, const wchar_t *value);
+
+protected:
+ StringBuilder elementString;
+ ifc_devicetypeeditor *editor;
+ BOOL hitList[TYPE_TAG_MAX];
+ SIZE iconSize;
+
+protected:
+ RECVS_DISPATCH;
+
+};
+
+
+#endif // _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_TYPE_PARSER_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceView.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceView.cpp
new file mode 100644
index 00000000..86ab3cc4
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceView.cpp
@@ -0,0 +1,994 @@
+#include "main.h"
+#include "./deviceView.h"
+#include <wincodec.h>
+
+#include <commctrl.h>
+#include <strsafe.h>
+#include <vector>
+#include <algorithm>
+
+#define DEVICEVIEW_PROP L"NullsoftDevicesViewProp"
+static ATOM DEVICEVIEW_ATOM = 0;
+
+typedef struct DeviceView
+{
+ Device *device;
+} DeviceView;
+
+
+typedef std::vector<DeviceIconInfo*> DeviceIconInfoList;
+
+static INT_PTR
+DeviceView_DialogProc(HWND hwnd, unsigned int uMsg, WPARAM wParam, LPARAM lParam);
+
+
+#define DEVICEVIEW(_hwnd) ((DeviceView*)GetPropW((_hwnd), MAKEINTATOM(DEVICEVIEW_ATOM)))
+#define DEVICEVIEW_RET_VOID(_view, _hwnd) { (_view) = DEVICEVIEW((_hwnd)); if (NULL == (_view)) return; }
+#define DEVICEVIEW_RET_VAL(_view, _hwnd, _error) { (_view) = DEVICEVIEW((_hwnd)); if (NULL == (_view)) return (_error); }
+
+
+
+static HBITMAP
+DeviceView_HBitmapFromWicSource(IWICBitmapSource *wicSource)
+{
+ HRESULT hr;
+ HBITMAP bitmap;
+ BITMAPINFO bitmapInfo;
+ unsigned int width, height;
+ void *pixelData = NULL;
+ HDC windowDC;
+ unsigned int strideSize, imageSize;
+
+ if (NULL == wicSource)
+ return NULL;
+
+ hr = wicSource->GetSize(&width, &height);
+ if (FAILED(hr))
+ return NULL;
+
+ ZeroMemory(&bitmapInfo, sizeof(bitmapInfo));
+ bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ bitmapInfo.bmiHeader.biWidth = width;
+ bitmapInfo.bmiHeader.biHeight = -(LONG)height;
+ bitmapInfo.bmiHeader.biPlanes = 1;
+ bitmapInfo.bmiHeader.biBitCount = 32;
+ bitmapInfo.bmiHeader.biCompression = BI_RGB;
+
+ windowDC = GetDCEx(NULL, NULL, DCX_WINDOW | DCX_CACHE);
+
+ bitmap = CreateDIBSection(windowDC, &bitmapInfo, DIB_RGB_COLORS, &pixelData, NULL, 0);
+
+ if (NULL != windowDC)
+ ReleaseDC(NULL, windowDC);
+
+ if (NULL == bitmap)
+ return NULL;
+
+ hr = UIntMult(width, sizeof(DWORD), &strideSize);
+ if (SUCCEEDED(hr))
+ {
+ hr = UIntMult(strideSize, height, &imageSize);
+ if (SUCCEEDED(hr))
+ hr = wicSource->CopyPixels(NULL, strideSize, imageSize, (BYTE*)pixelData);
+ }
+
+ if (FAILED(hr))
+ {
+ DeleteObject(bitmap);
+ bitmap = NULL;
+ }
+
+ return bitmap;
+}
+
+static HBITMAP
+DeviceView_LoadIcon(const wchar_t *path)
+{
+ IWICImagingFactory *wicFactory;
+ IWICBitmapDecoder *wicDecoder;
+
+ HRESULT hr;
+ HBITMAP bitmap;
+
+ hr = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(&wicFactory));
+
+ if (FAILED(hr))
+ return NULL;
+
+ bitmap = NULL;
+
+ hr = wicFactory->CreateDecoderFromFilename(path, NULL, GENERIC_READ,
+ WICDecodeMetadataCacheOnDemand, &wicDecoder);
+
+ if (SUCCEEDED(hr))
+ {
+ IWICBitmapFrameDecode *wicFrame;
+ hr = wicDecoder->GetFrame(0, &wicFrame);
+ if(SUCCEEDED(hr))
+ {
+ IWICBitmapSource *wicSource;
+ hr = wicFrame->QueryInterface(IID_IWICBitmapSource,
+ reinterpret_cast<void **>(&wicSource));
+ if (SUCCEEDED(hr))
+ {
+ WICPixelFormatGUID pixelFormat;
+ hr = wicSource->GetPixelFormat(&pixelFormat);
+ if (FAILED(hr) ||
+ (GUID_WICPixelFormat32bppPBGRA != pixelFormat &&
+ GUID_WICPixelFormat32bppBGR != pixelFormat &&
+ GUID_WICPixelFormat32bppBGRA != pixelFormat &&
+ GUID_WICPixelFormat32bppRGBA != pixelFormat &&
+ GUID_WICPixelFormat32bppPRGBA != pixelFormat))
+ {
+ // try to convert
+ IWICFormatConverter *wicConverter;
+ hr = wicFactory->CreateFormatConverter(&wicConverter);
+ if (SUCCEEDED(hr))
+ {
+ hr = wicConverter->Initialize(wicSource, GUID_WICPixelFormat32bppPBGRA,
+ WICBitmapDitherTypeNone, NULL, 0.f, WICBitmapPaletteTypeCustom);
+
+ if (SUCCEEDED(hr))
+ {
+ IWICBitmapSource *wicConvertedSource;
+ hr = wicConverter->QueryInterface(IID_IWICBitmapSource,
+ reinterpret_cast<void **>(&wicConvertedSource));
+ if (SUCCEEDED(hr))
+ {
+ wicSource->Release();
+ wicSource = wicConvertedSource;
+ }
+ }
+ wicConverter->Release();
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ bitmap = DeviceView_HBitmapFromWicSource(wicSource);
+
+ wicSource->Release();
+ }
+ wicFrame->Release();
+ }
+ wicDecoder->Release();
+ }
+
+
+ wicFactory->Release();
+ return bitmap;
+
+}
+
+HWND
+DeviceView_CreateWindow(HWND parentWindow, Device *device)
+{
+ HWND hwnd;
+
+ if (NULL == device)
+ return NULL;
+
+ if (0 == DEVICEVIEW_ATOM)
+ {
+ DEVICEVIEW_ATOM = GlobalAddAtom(DEVICEVIEW_PROP);
+ if (0 == DEVICEVIEW_ATOM)
+ return NULL;
+
+ }
+
+ hwnd = WASABI_API_CREATEDIALOGPARAMW((INT_PTR)IDD_DEVICE_VIEW, parentWindow,
+ DeviceView_DialogProc, (LPARAM)device);
+
+ return hwnd;
+}
+
+static void
+DeviceView_InitCapacity(HWND hwnd)
+{
+ DeviceView *self;
+ HWND controlWindow;
+ uint64_t totalSpace, usedSpace;
+
+ DEVICEVIEW_RET_VOID(self, hwnd);
+
+ if (NULL != self->device)
+ {
+ if (FAILED(self->device->GetTotalSpace(&totalSpace)))
+ totalSpace = 0;
+
+ if (FAILED(self->device->GetUsedSpace(&usedSpace)))
+ usedSpace = 0;
+ }
+ else
+ {
+ totalSpace = 0;
+ usedSpace = 0;
+ }
+
+ controlWindow = GetDlgItem(hwnd, IDC_SPIN_TOTALSPACE);
+ if (NULL != controlWindow)
+ {
+ SendMessage(controlWindow, UDM_SETRANGE32, (WPARAM)0, (LPARAM)0xFFFFFF);
+ SendMessage(controlWindow, UDM_SETPOS32, (WPARAM)0, (LPARAM)totalSpace);
+ }
+
+ controlWindow = GetDlgItem(hwnd, IDC_SPIN_USEDSPACE);
+ if (NULL != controlWindow)
+ {
+ SendMessage(controlWindow, UDM_SETRANGE32, (WPARAM)0, (LPARAM)totalSpace);
+ SendMessage(controlWindow, UDM_SETPOS32, (WPARAM)0, (LPARAM)usedSpace);
+ }
+
+}
+
+static int
+DeviceView_CompareDeviceIconInfo(const void *elem1, const void *elem2)
+{
+ DeviceIconInfo *info1;
+ DeviceIconInfo *info2;
+
+ info1 = *((DeviceIconInfo**)elem1);
+ info2 = *((DeviceIconInfo**)elem2);
+
+ if (NULL == info1 || NULL == info2)
+ return (int)(info1 - info2);
+ if (info1->width != info2->width)
+ return (int)(info1->width - info2->width);
+
+ if (info1->height != info2->height)
+ return (int)(info1->height - info2->height);
+
+ if (NULL == info1->path || NULL == info2->path)
+ return (int)(info1->path - info2->path);
+
+ return CompareString(CSTR_INVARIANT, NORM_IGNORECASE, info1->path, -1, info2->path, -1) - 2;
+}
+
+static int
+DeviceView_CompareDeviceIconInfo_V2(const void* elem1, const void* elem2)
+{
+ return DeviceView_CompareDeviceIconInfo(elem1, elem2) < 0;
+}
+
+static BOOL
+DeviceView_EnumerateIcons(const wchar_t *path, unsigned int width, unsigned int height, void *user)
+{
+ DeviceIconInfo *info;
+
+ DeviceIconInfoList *list = (DeviceIconInfoList*)user;
+ if( NULL == list)
+ return FALSE;
+
+ info = (DeviceIconInfo*)malloc(sizeof(DeviceIconInfo));
+ if (NULL != info)
+ {
+ info->height = height;
+ info->width = width;
+ info->path = String_Duplicate(path);
+
+ list->push_back(info);
+ }
+
+
+ return TRUE;
+}
+
+static void
+DeviceView_DestroyIcons(HWND hwnd)
+{
+ HWND controlWindow;
+ int count, index;
+ DeviceIconInfo *info;
+
+
+ controlWindow = GetDlgItem(hwnd, IDC_COMBO_ICONS);
+ if (NULL == controlWindow)
+ return;
+
+ count = (int)SendMessage(controlWindow, CB_GETCOUNT, 0, 0L);
+ for(index = 0; index < count; index++)
+ {
+ info = (DeviceIconInfo*)SendMessage(controlWindow, CB_GETITEMDATA, index, 0L);
+ if (CB_ERR != (INT_PTR)info)
+ {
+ String_Free(info->path);
+ free(info);
+ }
+ }
+
+ SendMessage(controlWindow, CB_RESETCONTENT, 0, 0L);
+
+ controlWindow = GetDlgItem(hwnd, IDC_STATIC_PREVIEWICON);
+ if (NULL != controlWindow)
+ {
+ HBITMAP bitmap;
+ bitmap = (HBITMAP)SendMessage(controlWindow, STM_GETIMAGE, IMAGE_BITMAP, 0L);
+ if(NULL != bitmap)
+ DeleteObject(bitmap);
+ }
+
+}
+
+static void
+DeviceView_InitIcons(HWND hwnd, const wchar_t *selectPath)
+{
+ DeviceView *self;
+ DeviceIconInfoList list;
+ HWND controlWindow;
+ wchar_t buffer[2048];
+
+ DEVICEVIEW_RET_VOID(self, hwnd);
+
+ DeviceView_DestroyIcons(hwnd);
+
+ controlWindow = GetDlgItem(hwnd, IDC_COMBO_ICONS);
+ if (NULL != controlWindow)
+ {
+ size_t index, count;
+ int iItem, iSelect;
+ DeviceIconInfo *info;
+
+ if (NULL != self->device)
+ {
+ self->device->EnumerateIcons(DeviceView_EnumerateIcons, &list);
+ }
+
+ count = list.size();
+ //qsort(list.begin(), count, sizeof(DeviceIconInfo**), DeviceView_CompareDeviceIconInfo);
+ std::sort(list.begin(), list.end(), DeviceView_CompareDeviceIconInfo_V2);
+
+ iSelect = 0;
+
+ for(index = 0; index < count; index++)
+ {
+ info = list[index];
+ if (1 == info->width && 1 == info->height)
+ {
+ StringCchPrintf(buffer, ARRAYSIZE(buffer), L"%s",
+ info->path);
+ }
+ else
+ {
+ StringCchPrintf(buffer, ARRAYSIZE(buffer), L"[%dx%d] - %s",
+ info->width, info->height, info->path);
+ }
+
+ iItem = (int)SendMessage(controlWindow, CB_ADDSTRING, 0, (LPARAM)buffer);
+ if (CB_ERR != iItem)
+ {
+ if (CB_ERR == (int)SendMessage(controlWindow, CB_SETITEMDATA, index, (LPARAM)info))
+ {
+ SendMessage(controlWindow, CB_DELETESTRING, index, 0L);
+ iItem = CB_ERR;
+ }
+ }
+
+ if (CB_ERR == iItem)
+ {
+ free(info);
+ }
+ else if (NULL != selectPath &&
+ CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE,
+ info->path, -1, selectPath, -1))
+ {
+ iSelect = iItem;
+ }
+ }
+
+ iItem = (int)SendMessage(controlWindow, CB_GETCOUNT, 0, 0L);
+ if (CB_ERR == iItem)
+ iItem = 0;
+ EnableWindow(GetDlgItem(hwnd, IDC_BUTTON_EDITICON), (0 != iItem));
+ EnableWindow(GetDlgItem(hwnd, IDC_BUTTON_REMOVEICON),(0 != iItem));
+
+
+ SendMessage(controlWindow, CB_SETCURSEL, iSelect, 0L);
+ PostMessage(hwnd, WM_COMMAND, MAKEWPARAM(IDC_COMBO_ICONS, CBN_SELENDOK), (LPARAM)controlWindow);
+ }
+}
+
+static void
+DeviceView_NewIcon(HWND hwnd)
+{
+ DeviceView *self;
+ DeviceIconInfo info;
+ INT_PTR result;
+
+ DEVICEVIEW_RET_VOID(self, hwnd);
+
+ ZeroMemory(&info, sizeof(info));
+
+ result = DeviceIconEditor_Show(hwnd, &info);
+ if (IDOK == result)
+ {
+ if (NULL != self->device &&
+ SUCCEEDED(self->device->AddIcon(info.path, info.width, info.height)))
+ {
+ DeviceView_InitIcons(hwnd, info.path);
+ }
+
+ String_Free(info.path);
+ }
+}
+
+static void
+DeviceView_RemoveIcon(HWND hwnd)
+{
+ DeviceView *self;
+ DeviceIconInfo *info;
+ int index, count;
+ HWND controlWindow;
+
+ DEVICEVIEW_RET_VOID(self, hwnd);
+
+ controlWindow = GetDlgItem(hwnd, IDC_COMBO_ICONS);
+ if (NULL == controlWindow)
+ return;
+
+ index = (int)SendMessage(controlWindow, CB_GETCURSEL, 0, 0L);
+ if (CB_ERR == index)
+ return;
+
+ info = (DeviceIconInfo*)SendMessage(controlWindow, CB_GETITEMDATA, index, 0L);
+ if (CB_ERR != (INT_PTR)info)
+ {
+ if (NULL != self->device)
+ self->device->RemoveIcon(info->width, info->height);
+
+ String_Free(info->path);
+ free(info);
+ }
+
+ SendMessage(controlWindow, CB_DELETESTRING, index, 0L);
+
+ count = (int)SendMessage(controlWindow, CB_GETCOUNT, 0, 0L);
+ if (count > 0)
+ {
+ if (index > count)
+ index = count - 1;
+ SendMessage(controlWindow, CB_SETCURSEL, index, 0L);
+ }
+ else
+ {
+ SendMessage(controlWindow, CB_SETCURSEL, -1, 0L);
+ EnableWindow(GetDlgItem(hwnd, IDC_BUTTON_EDITICON),FALSE);
+ EnableWindow(GetDlgItem(hwnd, IDC_BUTTON_REMOVEICON),FALSE);
+ }
+
+ PostMessage(hwnd, WM_COMMAND, MAKEWPARAM(IDC_COMBO_ICONS, CBN_SELENDOK), (LPARAM)controlWindow);
+
+ UpdateWindow(controlWindow);
+}
+
+static void
+DeviceView_EditIcon(HWND hwnd)
+{
+ DeviceView *self;
+ DeviceIconInfo *info;
+ int index;
+ unsigned int width, height;
+ HWND controlWindow;
+ INT_PTR result;
+
+ DEVICEVIEW_RET_VOID(self, hwnd);
+
+ controlWindow = GetDlgItem(hwnd, IDC_COMBO_ICONS);
+ if (NULL == controlWindow)
+ return;
+
+ index = (int)SendMessage(controlWindow, CB_GETCURSEL, 0, 0L);
+ if (CB_ERR == index)
+ return;
+
+ info = (DeviceIconInfo*)SendMessage(controlWindow, CB_GETITEMDATA, index, 0L);
+ if (CB_ERR == (INT_PTR)info)
+ return;
+ width = info->width;
+ height = info->height;
+
+ result = DeviceIconEditor_Show(hwnd, info);
+ if (IDOK == result)
+ {
+ if (NULL != self->device)
+ {
+ self->device->RemoveIcon(width, height);
+ self->device->AddIcon(info->path, info->width, info->height);
+ DeviceView_InitIcons(hwnd, info->path);
+ }
+ }
+
+ UpdateWindow(controlWindow);
+}
+
+static void
+DeviceView_InitView(HWND hwnd)
+{
+ DeviceView *self;
+ HWND controlWindow;
+ wchar_t buffer[1024];
+
+ DEVICEVIEW_RET_VOID(self, hwnd);
+
+ controlWindow = GetDlgItem(hwnd, IDC_EDIT_NAME);
+ if (NULL != controlWindow)
+ {
+ if (NULL == self->device ||
+ 0 == MultiByteToWideChar(CP_UTF8, 0, self->device->GetName(), -1, buffer, ARRAYSIZE(buffer)))
+ {
+ StringCchCopy(buffer, ARRAYSIZE(buffer), L"<unknown>");
+ }
+ SetWindowText(controlWindow, buffer);
+ }
+
+ controlWindow = GetDlgItem(hwnd, IDC_EDIT_TITLE);
+ if (NULL != controlWindow)
+ {
+ if (NULL == self->device ||
+ FAILED(self->device->GetDisplayName(buffer, ARRAYSIZE(buffer))))
+ {
+ StringCchCopy(buffer, ARRAYSIZE(buffer), L"<unknown>");
+ }
+ SetWindowText(controlWindow, buffer);
+ }
+
+ controlWindow = GetDlgItem(hwnd, IDC_EDIT_TYPE);
+ if (NULL != controlWindow)
+ {
+ buffer[0] = L'\0';
+
+ if (NULL != self->device)
+ {
+ const char *typeName;
+ ifc_devicetype *type;
+
+ typeName = self->device->GetType();
+
+ if (NULL != WASABI_API_DEVICES &&
+ S_OK == WASABI_API_DEVICES->TypeFind(typeName, &type))
+ {
+ if (FAILED(type->GetDisplayName(buffer, ARRAYSIZE(buffer))))
+ buffer[0] = L'\0';
+
+ type->Release();
+ }
+
+ if (L'\0' == *buffer)
+ MultiByteToWideChar(CP_UTF8, 0, typeName, -1, buffer, ARRAYSIZE(buffer));
+ }
+
+ if (L'\0' == *buffer)
+ StringCchCopy(buffer, ARRAYSIZE(buffer), L"<unknown>");
+
+ SetWindowText(controlWindow, buffer);
+ }
+
+ controlWindow = GetDlgItem(hwnd, IDC_EDIT_CONNECTION);
+ if (NULL != controlWindow)
+ {
+ buffer[0] = L'\0';
+
+ if (NULL != self->device)
+ {
+ const char *connectionName;
+ ifc_deviceconnection *connection;
+
+ connectionName = self->device->GetConnection();
+
+ if (NULL != WASABI_API_DEVICES &&
+ S_OK == WASABI_API_DEVICES->ConnectionFind(connectionName, &connection))
+ {
+ if (FAILED(connection->GetDisplayName(buffer, ARRAYSIZE(buffer))))
+ buffer[0] = L'\0';
+
+ connection->Release();
+ }
+
+ if (L'\0' == *buffer)
+ MultiByteToWideChar(CP_UTF8, 0, connectionName, -1, buffer, ARRAYSIZE(buffer));
+ }
+
+ if (L'\0' == *buffer)
+ StringCchCopy(buffer, ARRAYSIZE(buffer), L"<unknown>");
+
+ SetWindowText(controlWindow, buffer);
+ }
+
+ controlWindow = GetDlgItem(hwnd, IDC_COMBO_ATTACHED);
+ if (NULL != controlWindow)
+ {
+ SendMessage(controlWindow, CB_RESETCONTENT, 0, 0L);
+
+ if (NULL != self->device)
+ {
+ const wchar_t *searchString;
+
+ SendMessage(controlWindow, CB_ADDSTRING, 0, (LPARAM)L"Yes");
+ SendMessage(controlWindow, CB_ADDSTRING, 0, (LPARAM)L"No");
+
+ if (FALSE == self->device->GetAttached())
+ searchString = L"No";
+ else
+ searchString = L"Yes";
+
+ SendMessage(controlWindow, CB_SELECTSTRING, -1, (LPARAM)searchString);
+ }
+ else
+ {
+ SendMessage(controlWindow, CB_ADDSTRING, 0, (LPARAM)L"<unknown>");
+ SendMessage(controlWindow, CB_SETCURSEL, 0, 0L);
+ }
+
+ }
+
+ controlWindow = GetDlgItem(hwnd, IDC_COMBO_VISIBLE);
+ if (NULL != controlWindow)
+ {
+ SendMessage(controlWindow, CB_RESETCONTENT, 0, 0L);
+
+ if (NULL != self->device)
+ {
+ const wchar_t *searchString;
+
+ SendMessage(controlWindow, CB_ADDSTRING, 0, (LPARAM)L"Yes");
+ SendMessage(controlWindow, CB_ADDSTRING, 0, (LPARAM)L"No");
+
+ if (FALSE != self->device->GetHidden())
+ searchString = L"No";
+ else
+ searchString = L"Yes";
+
+ SendMessage(controlWindow, CB_SELECTSTRING, -1, (LPARAM)searchString);
+ }
+ else
+ {
+ SendMessage(controlWindow, CB_ADDSTRING, 0, (LPARAM)L"<unknown>");
+ SendMessage(controlWindow, CB_SETCURSEL, 0, 0L);
+ }
+ }
+
+ DeviceView_InitCapacity(hwnd);
+ DeviceView_InitIcons(hwnd, NULL);
+
+}
+
+static INT_PTR
+DeviceView_OnInitDialog(HWND hwnd, HWND focusWindow, LPARAM param)
+{
+ DeviceView *self;
+
+ self = (DeviceView*)malloc(sizeof(DeviceView));
+ if (NULL != self)
+ {
+ ZeroMemory(self, sizeof(DeviceView));
+ if (FALSE == SetProp(hwnd, MAKEINTATOM(DEVICEVIEW_ATOM), self))
+ {
+ free(self);
+ self = NULL;
+ }
+ }
+
+ if (NULL == self)
+ {
+ DestroyWindow(hwnd);
+ return 0;
+ }
+
+ self->device = (Device*)param;
+ if (NULL != self->device)
+ self->device->AddRef();
+
+ DeviceView_InitView(hwnd);
+
+ SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_FRAMECHANGED);
+
+ return 0;
+}
+
+static void
+DeviceView_OnDestroy(HWND hwnd)
+{
+ DeviceView *self;
+
+ DeviceView_DestroyIcons(hwnd);
+
+ self = DEVICEVIEW(hwnd);
+ RemoveProp(hwnd, MAKEINTATOM(DEVICEVIEW_ATOM));
+
+ if (NULL == self)
+ return;
+
+ if (NULL != self->device)
+ self->device->Release();
+
+ free(self);
+}
+
+static void
+DeviceView_OnTitleEditChanged(HWND hwnd)
+{
+ DeviceView *self;
+ HWND controlWindow;
+ wchar_t buffer[1024];
+
+ DEVICEVIEW_RET_VOID(self, hwnd);
+
+ controlWindow = GetDlgItem(hwnd, IDC_EDIT_TITLE);
+ if (NULL == controlWindow)
+ return;
+
+ if (NULL == self->device)
+ return;
+
+ GetWindowText(controlWindow, buffer, ARRAYSIZE(buffer));
+
+ self->device->SetDisplayName(buffer);
+
+}
+
+static void
+DeviceView_OnAttachedComboChanged(HWND hwnd)
+{
+ DeviceView *self;
+ HWND controlWindow;
+ wchar_t buffer[1024];
+ int index;
+
+ DEVICEVIEW_RET_VOID(self, hwnd);
+
+ controlWindow = GetDlgItem(hwnd, IDC_COMBO_ATTACHED);
+ if (NULL == controlWindow)
+ return;
+
+ if (NULL == self->device)
+ return;
+
+ index = (int)SendMessage(controlWindow, CB_GETCURSEL, 0, 0);
+ if (CB_ERR != index &&
+ CB_ERR != SendMessage(controlWindow, CB_GETLBTEXT, index, (LPARAM)buffer))
+ {
+ if (CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, buffer, -1, L"Yes", -1))
+ {
+ self->device->Attach(NULL);
+ }
+ else if (CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, buffer, -1, L"No", -1))
+ {
+ self->device->Detach(NULL);
+ }
+ }
+}
+
+static void
+DeviceView_OnVisibleComboChanged(HWND hwnd)
+{
+ DeviceView *self;
+ HWND controlWindow;
+ wchar_t buffer[1024];
+ int index;
+
+ DEVICEVIEW_RET_VOID(self, hwnd);
+
+ controlWindow = GetDlgItem(hwnd, IDC_COMBO_VISIBLE);
+ if (NULL == controlWindow)
+ return;
+
+ if (NULL == self->device)
+ return;
+
+ index = (int)SendMessage(controlWindow, CB_GETCURSEL, 0, 0);
+ if (-1 != index &&
+ CB_ERR != SendMessage(controlWindow, CB_GETLBTEXT, index, (LPARAM)buffer))
+ {
+ if (CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, buffer, -1, L"Yes", -1))
+ {
+ self->device->SetHidden(FALSE);
+ }
+ else if (CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, buffer, -1, L"No", -1))
+ {
+ self->device->SetHidden(TRUE);
+ }
+ }
+}
+
+static void
+DeviceView_OnIconsComboChanged(HWND hwnd)
+{
+ DeviceView *self;
+ HWND controlWindow, pictureWindow;
+ int index;
+ HBITMAP bitmap, previousBitmap;
+
+ DEVICEVIEW_RET_VOID(self, hwnd);
+
+ controlWindow = GetDlgItem(hwnd, IDC_COMBO_ICONS);
+ if (NULL == controlWindow)
+ return;
+
+ if (NULL == self->device)
+ return;
+
+ pictureWindow = GetDlgItem(hwnd, IDC_STATIC_PREVIEWICON);
+ if (NULL == pictureWindow)
+ return;
+
+ bitmap = NULL;
+ index = (int)SendMessage(controlWindow, CB_GETCURSEL, 0, 0);\
+ if (CB_ERR != index)
+ {
+ DeviceIconInfo *info;
+ info = (DeviceIconInfo*)SendMessage(controlWindow, CB_GETITEMDATA, index, 0L);
+ if (CB_ERR != (INT_PTR)info && NULL != info->path)
+ {
+ bitmap = DeviceView_LoadIcon(info->path);
+ }
+ }
+
+ previousBitmap = (HBITMAP)SendMessage(pictureWindow, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)bitmap);
+ if (NULL != previousBitmap)
+ DeleteObject(previousBitmap);
+
+ previousBitmap = (HBITMAP)SendMessage(pictureWindow, STM_GETIMAGE, IMAGE_BITMAP, 0L);
+ if(previousBitmap != bitmap)
+ {
+ if (NULL != bitmap)
+ DeleteObject(bitmap);
+ }
+
+
+}
+
+static void
+DeviceView_OnTotalSpaceEditChanged(HWND hwnd)
+{
+ DeviceView *self;
+ HWND controlWindow;
+ uint64_t totalSpace;
+ BOOL error;
+
+ DEVICEVIEW_RET_VOID(self, hwnd);
+
+ controlWindow = GetDlgItem(hwnd, IDC_SPIN_TOTALSPACE);
+ if (NULL == controlWindow)
+ return;
+
+ if (NULL == self->device)
+ return;
+
+ totalSpace = (size_t)SendMessage(controlWindow, UDM_GETPOS32, 0, (LPARAM)&error);
+ if (FALSE != error)
+ return;
+
+ if (FAILED(self->device->SetTotalSpace(totalSpace)))
+ {
+ if (FAILED(self->device->GetTotalSpace(&totalSpace)))
+ totalSpace = 0;
+ }
+
+ controlWindow = GetDlgItem(hwnd, IDC_SPIN_USEDSPACE);
+ if (NULL != controlWindow)
+ {
+ SendMessage(controlWindow, UDM_SETRANGE32, (WPARAM)0, (LPARAM)totalSpace);
+ }
+}
+
+static void
+DeviceView_OnUsedSpaceEditChanged(HWND hwnd)
+{
+ DeviceView *self;
+ HWND controlWindow;
+ uint64_t usedSpace;
+ BOOL error;
+
+ DEVICEVIEW_RET_VOID(self, hwnd);
+
+ controlWindow = GetDlgItem(hwnd, IDC_SPIN_USEDSPACE);
+ if (NULL == controlWindow)
+ return;
+
+ if (NULL == self->device)
+ return;
+
+ usedSpace = (size_t)SendMessage(controlWindow, UDM_GETPOS32, 0, (LPARAM)&error);
+ if (FALSE != error)
+ return;
+
+ if (FAILED(self->device->SetUsedSpace(usedSpace)))
+ {
+ if (FAILED(self->device->GetTotalSpace(&usedSpace)))
+ usedSpace = 0;
+ }
+}
+
+static void
+DeviceView_OnCommand(HWND hwnd, INT commandId, INT eventId, HWND controlWindow)
+{
+ switch(commandId)
+ {
+ case IDC_EDIT_TITLE:
+ switch(eventId)
+ {
+ case EN_CHANGE:
+ DeviceView_OnTitleEditChanged(hwnd);
+ break;
+ }
+ break;
+ case IDC_COMBO_ATTACHED:
+ switch(eventId)
+ {
+ case CBN_SELENDOK:
+ DeviceView_OnAttachedComboChanged(hwnd);
+ break;
+ }
+ break;
+
+ case IDC_COMBO_VISIBLE:
+ switch(eventId)
+ {
+ case CBN_SELENDOK:
+ DeviceView_OnVisibleComboChanged(hwnd);
+ break;
+ }
+ break;
+ case IDC_COMBO_ICONS:
+ switch(eventId)
+ {
+ case CBN_SELENDOK:
+ DeviceView_OnIconsComboChanged(hwnd);
+ break;
+ }
+ break;
+ case IDC_EDIT_TOTALSPACE:
+ switch(eventId)
+ {
+ case EN_CHANGE:
+ DeviceView_OnTotalSpaceEditChanged(hwnd);
+ break;
+ }
+ break;
+ case IDC_EDIT_USEDSPACE:
+ switch(eventId)
+ {
+ case EN_CHANGE:
+ DeviceView_OnUsedSpaceEditChanged(hwnd);
+ break;
+ }
+ break;
+ case IDC_BUTTON_NEWICON:
+ switch(eventId)
+ {
+ case BN_CLICKED:
+ DeviceView_NewIcon(hwnd);
+ break;
+ }
+ break;
+ case IDC_BUTTON_REMOVEICON:
+ switch(eventId)
+ {
+ case BN_CLICKED:
+ DeviceView_RemoveIcon(hwnd);
+ break;
+ }
+ break;
+ case IDC_BUTTON_EDITICON:
+ switch(eventId)
+ {
+ case BN_CLICKED:
+ DeviceView_EditIcon(hwnd);
+ break;
+ }
+ break;
+ }
+}
+
+
+static INT_PTR
+DeviceView_DialogProc(HWND hwnd, unsigned int uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return DeviceView_OnInitDialog(hwnd, (HWND)wParam, lParam);
+ case WM_DESTROY: DeviceView_OnDestroy(hwnd); return TRUE;
+ case WM_COMMAND: DeviceView_OnCommand(hwnd, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); return TRUE;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceView.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceView.h
new file mode 100644
index 00000000..394a9978
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/deviceView.h
@@ -0,0 +1,15 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_VIEW_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_VIEW_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+HWND
+DeviceView_CreateWindow(HWND parentWindow,
+ Device *device);
+
+
+#endif //_NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_DEVICE_VIEW_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/gen_deviceprovider.sln b/Src/Plugins/Library/ml_devices/gen_deviceprovider/gen_deviceprovider.sln
new file mode 100644
index 00000000..9b1c4e3f
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/gen_deviceprovider.sln
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/gen_deviceprovider.vcproj b/Src/Plugins/Library/ml_devices/gen_deviceprovider/gen_deviceprovider.vcproj
new file mode 100644
index 00000000..24f71f98
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/gen_deviceprovider.vcproj
@@ -0,0 +1,415 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="gen_deviceprovider"
+ ProjectGUID="{A3C7D830-CD01-4C2B-9E1A-FB62B5236BA9}"
+ RootNamespace="gen_deviceprovider"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="196613"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="2"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories="../..;../../wasabi;../../agave"
+ PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;GEN_DEVICEPROVIDER_EXPORTS"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="0"
+ PrecompiledHeaderThrough=""
+ WarningLevel="3"
+ DebugInformationFormat="4"
+ EnablePREfast="false"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="comctl32.lib shlwapi.lib windowscodecs.lib"
+ OutputFile="$(ProgramFiles)\winamp\plugins\gen_deviceprovider.dll"
+ LinkIncremental="2"
+ IgnoreDefaultLibraryNames="msvcprt.lib"
+ GenerateDebugInformation="true"
+ ProgramDatabaseFile="$(OutDir)\$(TargetName).pdb"
+ SubSystem="2"
+ SupportUnloadOfDelayLoadedDLL="true"
+ ImportLibrary="$(OutDir)\$(TargetName).lib"
+ TargetMachine="1"
+ Profile="false"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ OutputDocumentFile="$(OutDir)\$(TargetName).xml"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="2"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ AdditionalIncludeDirectories="../..;../../wasabi;../../agave"
+ PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;GEN_DEVICEPROVIDER_EXPORTS"
+ RuntimeLibrary="2"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ PrecompiledHeaderThrough="main.h"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="comctl32.lib shlwapi.lib windowscodecs.lib"
+ OutputFile="$(ProgramFiles)\winamp\plugins\gen_deviceprovider.dll"
+ LinkIncremental="1"
+ IgnoreDefaultLibraryNames="msvcprt.lib"
+ GenerateDebugInformation="true"
+ ProgramDatabaseFile="$(OutDir)\$(TargetName).pdb"
+ SubSystem="2"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ SupportUnloadOfDelayLoadedDLL="true"
+ ImportLibrary="$(OutDir)\$(TargetName).lib"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ OutputDocumentFile="$(OutDir)\$(TargetName).xml"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="nu"
+ >
+ <File
+ RelativePath="..\..\nu\PtrList.cpp"
+ >
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\nu\PtrList.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\nu\trace.cpp"
+ >
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\nu\trace.h"
+ >
+ </File>
+ </Filter>
+ <File
+ RelativePath=".\common.h"
+ >
+ </File>
+ <File
+ RelativePath=".\device.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\device.h"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceActivity.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceActivity.h"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceCommandNodeParser.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceCommandNodeParser.h"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceCommandParser.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceCommandParser.h"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceConnectionNodeParser.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceConnectionNodeParser.h"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceConnectionParser.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceConnectionParser.h"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceIconEditor.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceIconEditor.h"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceNodeParser.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceNodeParser.h"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceParser.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceParser.h"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceTypeNodeParser.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceTypeNodeParser.h"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceTypeParser.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceTypeParser.h"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceView.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\deviceView.h"
+ >
+ </File>
+ <File
+ RelativePath=".\iconStore.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\iconStore.h"
+ >
+ </File>
+ <File
+ RelativePath=".\main.cpp"
+ >
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\main.h"
+ >
+ </File>
+ <File
+ RelativePath=".\plugin.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\plugin.h"
+ >
+ </File>
+ <File
+ RelativePath=".\provider.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\provider.h"
+ >
+ </File>
+ <File
+ RelativePath=".\resource.h"
+ >
+ </File>
+ <File
+ RelativePath=".\resources.rc"
+ >
+ </File>
+ <File
+ RelativePath=".\stringBuilder.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\stringBuilder.h"
+ >
+ </File>
+ <File
+ RelativePath=".\strings.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\strings.h"
+ >
+ </File>
+ <File
+ RelativePath=".\testSuite.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\testSuite.h"
+ >
+ </File>
+ <File
+ RelativePath=".\testSuiteLoader.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\testSuiteLoader.h"
+ >
+ </File>
+ <File
+ RelativePath=".\wasabi.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\wasabi.h"
+ >
+ </File>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/gen_deviceprovider.vcxproj b/Src/Plugins/Library/ml_devices/gen_deviceprovider/gen_deviceprovider.vcxproj
new file mode 100644
index 00000000..947430cd
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/gen_deviceprovider.vcxproj
@@ -0,0 +1,335 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{A3C7D830-CD01-4C2B-9E1A-FB62B5236BA9}</ProjectGuid>
+ <RootNamespace>gen_deviceprovider</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <IncludePath>$(IncludePath)</IncludePath>
+ <LibraryPath>$(LibraryPath)</LibraryPath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <IncludePath>$(IncludePath)</IncludePath>
+ <LibraryPath>$(LibraryPath)</LibraryPath>
+ <EmbedManifest>true</EmbedManifest>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg">
+ <VcpkgEnableManifest>false</VcpkgEnableManifest>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>../..;..\..\..\;..\..\..\..\wasabi;..\..\..\..\agave;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_DEBUG;_WINDOWS;_USRDLL;GEN_DEVICEPROVIDER_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <EnablePREfast>false</EnablePREfast>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;shlwapi.lib;windowscodecs.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <Profile>false</Profile>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <Xdcmake>
+ <OutputFile>$(IntDir)$(TargetName).xml</OutputFile>
+ </Xdcmake>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\
+xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>../..;..\..\..\..\;..\..\..\..\wasabi;..\..\..\..\agave;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_DEBUG;_WINDOWS;_USRDLL;GEN_DEVICEPROVIDER_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <EnablePREfast>false</EnablePREfast>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;shlwapi.lib;windowscodecs.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <Profile>false</Profile>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <Xdcmake>
+ <OutputFile>$(IntDir)$(TargetName).xml</OutputFile>
+ </Xdcmake>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\
+xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <Optimization>MaxSpeed</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <AdditionalIncludeDirectories>../..;..\..\..\..\;..\..\..\..\wasabi;..\..\..\..\agave;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;NDEBUG;_WINDOWS;_USRDLL;GEN_DEVICEPROVIDER_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <StringPooling>true</StringPooling>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;shlwapi.lib;windowscodecs.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <Xdcmake>
+ <OutputFile>$(IntDir)$(TargetName).xml</OutputFile>
+ </Xdcmake>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <Optimization>MaxSpeed</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <AdditionalIncludeDirectories>../..;..\..\..\..\;..\..\..\..\wasabi;..\..\..\..\agave;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;NDEBUG;_WINDOWS;_USRDLL;GEN_DEVICEPROVIDER_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <StringPooling>true</StringPooling>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;shlwapi.lib;windowscodecs.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <Xdcmake>
+ <OutputFile>$(IntDir)$(TargetName).xml</OutputFile>
+ </Xdcmake>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\..\nu\PtrList.cpp" />
+ <ClCompile Include="..\..\..\..\nu\trace.cpp" />
+ <ClCompile Include="device.cpp" />
+ <ClCompile Include="deviceActivity.cpp" />
+ <ClCompile Include="deviceCommandNodeParser.cpp" />
+ <ClCompile Include="deviceCommandParser.cpp" />
+ <ClCompile Include="deviceConnectionNodeParser.cpp" />
+ <ClCompile Include="deviceConnectionParser.cpp" />
+ <ClCompile Include="deviceIconEditor.cpp" />
+ <ClCompile Include="deviceNodeParser.cpp" />
+ <ClCompile Include="deviceParser.cpp" />
+ <ClCompile Include="deviceTypeNodeParser.cpp" />
+ <ClCompile Include="deviceTypeParser.cpp" />
+ <ClCompile Include="deviceView.cpp" />
+ <ClCompile Include="iconStore.cpp" />
+ <ClCompile Include="main.cpp" />
+ <ClCompile Include="plugin.cpp" />
+ <ClCompile Include="provider.cpp" />
+ <ClCompile Include="stringBuilder.cpp" />
+ <ClCompile Include="strings.cpp" />
+ <ClCompile Include="testSuite.cpp" />
+ <ClCompile Include="testSuiteLoader.cpp" />
+ <ClCompile Include="wasabi.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\..\..\nu\trace.h" />
+ <ClInclude Include="common.h" />
+ <ClInclude Include="device.h" />
+ <ClInclude Include="deviceActivity.h" />
+ <ClInclude Include="deviceCommandNodeParser.h" />
+ <ClInclude Include="deviceCommandParser.h" />
+ <ClInclude Include="deviceConnectionNodeParser.h" />
+ <ClInclude Include="deviceConnectionParser.h" />
+ <ClInclude Include="deviceIconEditor.h" />
+ <ClInclude Include="deviceNodeParser.h" />
+ <ClInclude Include="deviceParser.h" />
+ <ClInclude Include="deviceTypeNodeParser.h" />
+ <ClInclude Include="deviceTypeParser.h" />
+ <ClInclude Include="deviceView.h" />
+ <ClInclude Include="iconStore.h" />
+ <ClInclude Include="main.h" />
+ <ClInclude Include="plugin.h" />
+ <ClInclude Include="provider.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="stringBuilder.h" />
+ <ClInclude Include="strings.h" />
+ <ClInclude Include="testSuite.h" />
+ <ClInclude Include="testSuiteLoader.h" />
+ <ClInclude Include="wasabi.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="resources.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\..\Wasabi\Wasabi.vcxproj">
+ <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/gen_deviceprovider.vcxproj.filters b/Src/Plugins/Library/ml_devices/gen_deviceprovider/gen_deviceprovider.vcxproj.filters
new file mode 100644
index 00000000..517379d9
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/gen_deviceprovider.vcxproj.filters
@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="device.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="deviceActivity.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="deviceCommandNodeParser.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="deviceCommandParser.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="deviceConnectionNodeParser.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="deviceConnectionParser.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="deviceIconEditor.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="deviceNodeParser.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="deviceParser.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="deviceTypeNodeParser.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="deviceTypeParser.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="deviceView.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="iconStore.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="plugin.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="provider.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="stringBuilder.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="strings.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="testSuite.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="testSuiteLoader.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="wasabi.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\nu\trace.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\nu\PtrList.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="common.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="device.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="deviceActivity.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="deviceCommandNodeParser.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="deviceCommandParser.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="deviceConnectionNodeParser.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="deviceConnectionParser.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="deviceIconEditor.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="deviceNodeParser.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="deviceParser.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="deviceTypeNodeParser.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="deviceTypeParser.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="deviceView.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="iconStore.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="plugin.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="provider.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="stringBuilder.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="strings.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="testSuite.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="testSuiteLoader.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="wasabi.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\..\nu\trace.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{e264ea99-159e-4062-a78a-a85da3815f37}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{f5beaf0a-f21d-4ae9-8221-adde13e2b65d}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{207087f5-3450-4826-bae5-39064e9da43f}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="resources.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/iconStore.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/iconStore.cpp
new file mode 100644
index 00000000..28e916d8
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/iconStore.cpp
@@ -0,0 +1,289 @@
+#include "main.h"
+#include "./iconStore.h"
+
+#include <vector>
+
+
+typedef struct IconStoreRecord
+{
+ unsigned int width;
+ unsigned int height;
+ wchar_t *path;
+} IconStoreRecord;
+
+typedef std::vector<IconStoreRecord> RecodList;
+
+struct IconStore
+{
+ RecodList list;
+ wchar_t *basePath;
+};
+
+IconStore *
+IconStore_Create()
+{
+ IconStore *self;
+
+ self = new IconStore();
+ if (NULL == self)
+ return NULL;
+
+ self->basePath = NULL;
+ return self;
+}
+
+void
+IconStore_Destroy(IconStore *self)
+{
+ size_t index;
+ IconStoreRecord *record;
+
+ if (NULL == self)
+ return;
+
+ index = self->list.size();
+ while(index--)
+ {
+ record = &self->list[index];
+ String_Free(record->path);
+ }
+
+ String_Free(self->basePath);
+}
+
+BOOL
+IconStore_Add(IconStore *self, const wchar_t *path, unsigned int width, unsigned int height)
+{
+ IconStoreRecord record, *prec;
+ size_t index;
+
+ if (NULL == self)
+ return FALSE;
+
+ if(width < 1)
+ width = 1;
+
+ if(height < 1)
+ height = 1;
+
+ index = self->list.size();
+ while(index--)
+ {
+ prec = &self->list[index];
+ if (width == prec->width &&
+ height == prec->height)
+ {
+ String_Free(prec->path);
+ prec->path = String_Duplicate(path);
+ return TRUE;
+ }
+ }
+
+ record.path = String_Duplicate(path);
+ record.width = width;
+ record.height = height;
+
+ self->list.push_back(record);
+
+ return TRUE;
+}
+
+BOOL
+IconStore_RemovePath(IconStore *self, const wchar_t *path)
+{
+ size_t index;
+ IconStoreRecord *record;
+
+ if (NULL == self)
+ return FALSE;
+
+ index = self->list.size();
+ while(index--)
+ {
+ record = &self->list[index];
+ if(CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, record->path, -1, path, -1))
+ {
+ self->list.eraseAt(index);
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL
+IconStore_Remove(IconStore *self, unsigned int width, unsigned int height)
+{
+ size_t index;
+ IconStoreRecord *record;
+
+ if (NULL == self)
+ return FALSE;
+
+ index = self->list.size();
+ while(index--)
+ {
+ record = &self->list[index];
+ if(record->width == width &&
+ record->height == height)
+ {
+ self->list.eraseAt(index);
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL
+IconStore_GetFullPath(IconStore *self, const wchar_t *path, wchar_t *buffer, size_t bufferMax)
+{
+ if (NULL == buffer)
+ return FALSE;
+
+ if (NULL == self)
+ return FALSE;
+
+ if (FALSE != IS_STRING_EMPTY(path))
+ {
+ *buffer = L'\0';
+ return TRUE;
+ }
+
+ if (FALSE == PathIsRelative(path) ||
+ IS_STRING_EMPTY(self->basePath))
+ {
+ if (0 == String_CopyTo(buffer, path, bufferMax) &&
+ FALSE == IS_STRING_EMPTY(path))
+ return FALSE;
+ else
+ return TRUE;
+ }
+
+ if (NULL == PathCombine(buffer, self->basePath, path))
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL
+IconStore_Get(IconStore *self, wchar_t *buffer, size_t bufferMax, unsigned int width, unsigned int height)
+{
+ const wchar_t *icon;
+ IconStoreRecord *record;
+ size_t index;
+ double widthDbl, heightDbl;
+ double scaleMin, scaleHorz, scaleVert;
+
+ if (NULL == self)
+ return FALSE;
+
+ if (NULL == buffer)
+ return FALSE;
+
+ icon = NULL;
+
+ widthDbl = width;
+ heightDbl = height;
+
+ index = self->list.size();
+ if (index > 0)
+ {
+ record = &self->list[--index];
+ scaleHorz = widthDbl/record->width;
+ scaleVert = heightDbl/record->height;
+ scaleMin = (scaleHorz < scaleVert) ? scaleHorz : scaleVert;
+ icon = record->path;
+ if (1.0 != scaleMin)
+ {
+ scaleMin = fabs(1.0 - scaleMin);
+ while(index--)
+ {
+ record = &self->list[index];
+ scaleHorz = widthDbl/record->width;
+ scaleVert = heightDbl/record->height;
+ if (scaleHorz > scaleVert)
+ scaleHorz = scaleVert;
+
+ if (1.0 == scaleHorz)
+ {
+ icon = record->path;
+ break;
+ }
+
+ scaleHorz = fabs(1.0 - scaleHorz);
+ if (scaleHorz < scaleMin)
+ {
+ scaleMin = scaleHorz;
+ icon = record->path;
+ }
+ }
+ }
+ }
+
+ return IconStore_GetFullPath(self, icon, buffer, bufferMax);
+}
+
+BOOL
+IconStore_SetBasePath(IconStore *self, const wchar_t *path)
+{
+ if (NULL == self)
+ return FALSE;
+
+ String_Free(self->basePath);
+ self->basePath = String_Duplicate(path);
+
+ return TRUE;
+}
+
+IconStore *
+IconStore_Clone(IconStore *self)
+{
+ size_t index;
+ IconStore *clone;
+ IconStoreRecord targetRec;
+ const IconStoreRecord *sourceRec;
+
+ if (NULL == self)
+ return NULL;
+
+ clone = IconStore_Create();
+ if (NULL == clone)
+ return NULL;
+
+ clone->basePath = String_Duplicate(self->basePath);
+ for(index = 0; index < self->list.size(); index++)
+ {
+ sourceRec = &self->list[index];
+ targetRec.path = String_Duplicate(sourceRec->path);
+ targetRec.width = sourceRec->width;
+ targetRec.height = sourceRec->height;
+ clone->list.push_back(targetRec);
+ }
+
+ return clone;
+}
+
+
+BOOL
+IconStore_Enumerate(IconStore *self, IconEnumerator callback, void *user)
+{
+ size_t index;
+ wchar_t buffer[MAX_PATH*2];
+ IconStoreRecord *record;
+
+ if (NULL == self || NULL == callback)
+ return FALSE;
+
+ index = self->list.size();
+ while(index--)
+ {
+ record = &self->list[index];
+ if (FALSE != IconStore_GetFullPath(self, record->path, buffer, ARRAYSIZE(buffer)))
+ {
+ if (FALSE == callback(buffer, record->width, record->height, user))
+ return TRUE;
+ }
+ }
+
+ return TRUE;
+
+}
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/iconStore.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/iconStore.h
new file mode 100644
index 00000000..6134d1fe
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/iconStore.h
@@ -0,0 +1,53 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_ICON_STORE_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_ICON_STORE_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+typedef struct IconStore IconStore;
+typedef BOOL (*IconEnumerator)(const wchar_t *path, unsigned int width, unsigned int height, void *user);
+
+IconStore *
+IconStore_Create();
+
+void
+IconStore_Destroy(IconStore *self);
+
+BOOL
+IconStore_Add(IconStore *self,
+ const wchar_t *path,
+ unsigned int width,
+ unsigned int height);
+
+BOOL
+IconStore_RemovePath(IconStore *self,
+ const wchar_t *path);
+
+BOOL
+IconStore_Remove(IconStore *self,
+ unsigned int width,
+ unsigned int height);
+
+BOOL
+IconStore_Get(IconStore *self,
+ wchar_t *buffer,
+ size_t bufferMax,
+ unsigned int width,
+ unsigned int height);
+
+BOOL
+IconStore_SetBasePath(IconStore *self,
+ const wchar_t *path);
+
+IconStore *
+IconStore_Clone(IconStore *self);
+
+BOOL
+IconStore_Enumerate(IconStore *self,
+ IconEnumerator callback,
+ void *user);
+
+#endif //_NULLSOFT_WINAMP_GEN_DEVICE_ICON_STORE_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/main.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/main.cpp
new file mode 100644
index 00000000..6894dc4a
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/main.cpp
@@ -0,0 +1,2 @@
+#include <initguid.h>
+#include "main.h"
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/main.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/main.h
new file mode 100644
index 00000000..ab1aea70
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/main.h
@@ -0,0 +1,31 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_MAIN_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_MAIN_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+#include "./common.h"
+#include "./plugin.h"
+#include "./strings.h"
+#include "./stringBuilder.h"
+#include "./wasabi.h"
+#include "./provider.h"
+#include "./resource.h"
+#include "./device.h"
+#include "./deviceActivity.h"
+#include "./deviceView.h"
+#include "./deviceIconEditor.h"
+#include "./testSuite.h"
+#include "./testSuiteLoader.h"
+#include "./iconStore.h"
+
+#include "../../winamp/wa_ipc.h"
+#include "../../nu/trace.h"
+
+#include <math.h>
+#include <shlwapi.h>
+
+#endif //_NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_MAIN_HEADER
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/plugin.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/plugin.cpp
new file mode 100644
index 00000000..f9365033
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/plugin.cpp
@@ -0,0 +1,114 @@
+#include "main.h"
+#include "../../winamp/gen.h"
+#include <strsafe.h>
+
+static INT
+Plugin_Init(void);
+
+static void
+Plugin_Quit(void);
+
+static void
+Plugin_Config(void);
+
+
+extern "C" winampGeneralPurposePlugin plugin =
+{
+ GPPHDR_VER,
+ 0,
+ Plugin_Init,
+ Plugin_Config,
+ Plugin_Quit,
+};
+
+static DeviceProvider *deviceProvider = NULL;
+
+HINSTANCE
+Plugin_GetInstance(void)
+{
+ return plugin.hDllInstance;
+}
+
+HWND
+Plugin_GetWinampWindow(void)
+{
+ return plugin.hwndParent;
+}
+
+static void
+Plugin_SetDescription()
+{
+ WCHAR szBuffer[256], szTemplate[256];
+
+ if (0 != plugin.description)
+ AnsiString_Free(plugin.description);
+
+ if (NULL != WASABI_API_LNG)
+ WASABI_API_LNGSTRINGW_BUF(IDS_PLUGIN_NAME, szTemplate, ARRAYSIZE(szTemplate));
+ else
+ szTemplate[0] = L'\0';
+
+ StringCchPrintf(szBuffer, ARRAYSIZE(szBuffer),
+ ((L'\0' != szTemplate[0]) ? szTemplate : L"Nullsoft Test Device Provider v%d.%d"),
+ PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR);
+
+ plugin.description = String_ToAnsi(CP_ACP, 0, szBuffer, -1, NULL, NULL);
+}
+
+static INT
+Plugin_Init(void)
+{
+
+ if (FALSE == Wasabi_InitializeFromWinamp(plugin.hDllInstance, plugin.hwndParent))
+ return 1;
+
+ Wasabi_LoadDefaultServices();
+
+ Plugin_SetDescription();
+
+ if (NULL == deviceProvider)
+ {
+ if (FAILED(DeviceProvider::CreateInstance(&deviceProvider)))
+ {
+ Wasabi_Release();
+ return 2;
+ }
+
+ deviceProvider->Register(WASABI_API_DEVICES);
+ }
+
+ return 0;
+}
+
+static void
+Plugin_Quit(void)
+{
+ if (0 != plugin.description)
+ {
+ AnsiString_Free(plugin.description);
+ plugin.description = 0;
+ }
+
+ if (NULL != deviceProvider)
+ {
+ deviceProvider->Unregister(WASABI_API_DEVICES);
+ deviceProvider->Release();
+ }
+
+ Wasabi_Release();
+}
+
+static void
+Plugin_Config(void)
+{
+}
+
+EXTERN_C __declspec(dllexport) winampGeneralPurposePlugin *
+winampGetGeneralPurposePlugin()
+{
+ if (0 == plugin.description)
+ {
+ Plugin_SetDescription();
+ }
+ return &plugin;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/plugin.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/plugin.h
new file mode 100644
index 00000000..955f5cf0
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/plugin.h
@@ -0,0 +1,21 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_PLUGIN_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_PLUGIN_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+#define PLUGIN_VERSION_MAJOR 1
+#define PLUGIN_VERSION_MINOR 1
+
+// {51B54A05-B711-4509-80AE-5A0AAA502FA5}
+DEFINE_GUID(PLUGIN_LANGUAGE_ID,
+0x51b54a05, 0xb711, 0x4509, 0x80, 0xae, 0x5a, 0xa, 0xaa, 0x50, 0x2f, 0xa5);
+
+
+HINSTANCE Plugin_GetInstance(void);
+HWND Plugin_GetWinampWindow(void);
+
+#endif //_NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_PLUGIN_HEADER
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/provider.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/provider.cpp
new file mode 100644
index 00000000..c4146a88
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/provider.cpp
@@ -0,0 +1,336 @@
+#include "main.h"
+#include "./provider.h"
+
+typedef struct DeviceProviderThreadParam
+{
+ api_devicemanager *manager;
+ DeviceProvider *provider;
+ HANDLE readyEvent;
+
+}DeviceProviderThreadParam;
+
+DeviceProvider::DeviceProvider()
+ : ref(1), discoveryThread(NULL), cancelEvent(NULL)
+{
+ wchar_t buffer[MAX_PATH * 2];
+ HINSTANCE module;
+
+ TestSuiteLoader loader;
+
+ InitializeCriticalSection(&lock);
+
+ module = Plugin_GetInstance();
+ if (0 == GetModuleFileName(module, buffer, ARRAYSIZE(buffer)) ||
+ FALSE == PathRemoveFileSpec(buffer))
+ {
+ buffer[0] = L'\0';
+ }
+ PathAppend(buffer, L"testprovider.xml");
+ loader.Load(buffer, &testSuite);
+}
+
+DeviceProvider::~DeviceProvider()
+{
+ CancelDiscovery();
+ DeleteCriticalSection(&lock);
+}
+
+HRESULT DeviceProvider::CreateInstance(DeviceProvider **instance)
+{
+ if (NULL == instance)
+ return E_POINTER;
+
+ *instance = new DeviceProvider();
+
+ if (NULL == *instance)
+ return E_OUTOFMEMORY;
+
+ return S_OK;
+}
+
+size_t DeviceProvider::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+size_t DeviceProvider::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+int DeviceProvider::QueryInterface(GUID interface_guid, void **object)
+{
+ if (NULL == object)
+ return E_POINTER;
+
+ if (IsEqualIID(interface_guid, IFC_DeviceProvider))
+ *object = static_cast<ifc_deviceprovider*>(this);
+ else
+ {
+ *object = NULL;
+ return E_NOINTERFACE;
+ }
+
+ if (NULL == *object)
+ return E_UNEXPECTED;
+
+ AddRef();
+ return S_OK;
+}
+
+DWORD DeviceProvider::DiscoveryThread(api_devicemanager *manager)
+{
+ DWORD waitResult;
+ DWORD deviceCount;
+ DWORD threadId;
+ DWORD sleepTime;
+ LARGE_INTEGER perfCounter;
+
+ deviceCount = 0;
+
+ threadId = GetCurrentThreadId();
+// aTRACE_FMT("[test provider] device discovery started (0x%X).\r\n", threadId);
+
+ manager->SetProviderActive(this, TRUE);
+
+ for(;;)
+ {
+ if (FALSE != QueryPerformanceCounter(&perfCounter))
+ srand(perfCounter.LowPart);
+ else
+ srand(GetTickCount());
+
+ sleepTime = (DWORD)((double)rand()/(RAND_MAX) * 0);
+
+ waitResult = WaitForSingleObject(cancelEvent, sleepTime);
+ if (WAIT_OBJECT_0 == waitResult)
+ break;
+ else if (WAIT_TIMEOUT == waitResult)
+ {
+ Device *device;
+
+ deviceCount++;
+// aTRACE_FMT("[test provider] creating new device[%d] (0x%X).\r\n", deviceCount, threadId);
+
+ device = testSuite.GetRandomDevice();
+ if (NULL != device)
+ {
+ device->Connect();
+ if (0 == manager->DeviceRegister((ifc_device**)&device, 1))
+ device->Disconnect();
+ }
+
+ if (4 == deviceCount)
+ break;
+ }
+ else
+ {
+// aTRACE_FMT("[test provider] error (0x%X).\r\n", threadId);
+ break;
+ }
+ }
+
+ EnterCriticalSection(&lock);
+
+ if (NULL != discoveryThread)
+ {
+ CloseHandle(discoveryThread);
+ discoveryThread = NULL;
+ }
+
+ if (NULL != cancelEvent)
+ {
+ CloseHandle(cancelEvent);
+ cancelEvent = NULL;
+ }
+
+// aTRACE_FMT("[test provider] device discovery finished (0x%X).\r\n", threadId);
+ LeaveCriticalSection(&lock);
+
+ manager->SetProviderActive(this, FALSE);
+
+ return 0;
+}
+
+static DWORD CALLBACK DeviceProvider_DiscoveryThreadStarter(void *user)
+{
+ DeviceProviderThreadParam *param;
+ DeviceProvider *provider;
+ api_devicemanager *manager;
+ DWORD result;
+
+ param = (DeviceProviderThreadParam*)user;
+ manager = param->manager;
+ provider = param->provider;
+
+ if (NULL != manager)
+ manager->AddRef();
+
+ if (NULL != param->readyEvent)
+ SetEvent(param->readyEvent);
+
+ if (NULL == manager)
+ return -1;
+
+ if (NULL != provider)
+ result = provider->DiscoveryThread(manager);
+ else
+ result = -2;
+
+ manager->Release();
+
+ return result;
+}
+
+HRESULT DeviceProvider::BeginDiscovery(api_devicemanager *manager)
+{
+ HRESULT hr;
+
+ if (NULL == manager)
+ return E_INVALIDARG;
+
+ EnterCriticalSection(&lock);
+
+ if (NULL != discoveryThread)
+ hr = E_PENDING;
+ else
+ {
+ hr = S_OK;
+ if (NULL == cancelEvent)
+ {
+ cancelEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (NULL == cancelEvent)
+ hr = E_FAIL;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ DeviceProviderThreadParam param;
+
+ param.provider = this;
+ param.manager = manager;
+ param.readyEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (NULL == param.readyEvent)
+ hr = E_FAIL;
+ else
+ {
+ DWORD threadId;
+
+ discoveryThread = CreateThread(NULL, 0, DeviceProvider_DiscoveryThreadStarter, &param, 0, &threadId);
+ if (NULL == discoveryThread)
+ hr = E_FAIL;
+ else
+ WaitForSingleObject(param.readyEvent, INFINITE);
+
+ CloseHandle(param.readyEvent);
+ }
+ }
+
+ if (FAILED(hr))
+ CancelDiscovery();
+ }
+
+ LeaveCriticalSection(&lock);
+
+ return hr;
+}
+
+HRESULT DeviceProvider::CancelDiscovery()
+{
+ HRESULT hr;
+ HANDLE threadHandle, eventHandle;
+
+ EnterCriticalSection(&lock);
+
+ threadHandle = discoveryThread;
+ eventHandle = cancelEvent;
+
+ discoveryThread = NULL;
+ cancelEvent = NULL;
+
+ LeaveCriticalSection(&lock);
+
+ if (NULL != threadHandle)
+ {
+ if (NULL != eventHandle)
+ SetEvent(eventHandle);
+
+ WaitForSingleObject(threadHandle, INFINITE);
+ CloseHandle(threadHandle);
+ hr = S_OK;
+ }
+ else
+ hr = S_FALSE;
+
+ if (NULL != eventHandle)
+ CloseHandle(eventHandle);
+
+ return hr;
+}
+
+HRESULT DeviceProvider::GetActive()
+{
+ HRESULT hr;
+
+ EnterCriticalSection(&lock);
+
+ hr = (NULL != discoveryThread) ? S_OK : S_FALSE;
+
+ LeaveCriticalSection(&lock);
+
+ return hr;
+}
+
+HRESULT DeviceProvider::Register(api_devicemanager *manager)
+{
+ HRESULT hr;
+
+ if (NULL == manager)
+ return E_POINTER;
+
+ hr = manager->RegisterProvider(this);
+ if (SUCCEEDED(hr))
+ {
+ testSuite.RegisterCommands(manager);
+ testSuite.RegisterTypes(manager);
+ testSuite.RegisterConnections(manager);
+ testSuite.RegisterDevices(manager);
+ }
+
+ return hr;
+}
+
+HRESULT DeviceProvider::Unregister(api_devicemanager *manager)
+{
+ HRESULT hr;
+
+ if (NULL == manager)
+ return E_POINTER;
+
+ hr = manager->UnregisterProvider(this);
+ testSuite.UnregisterTypes(manager);
+ testSuite.UnregisterConnections(manager);
+ testSuite.UnregisterCommands(manager);
+ testSuite.UnregisterDevices(manager);
+
+ return hr;
+}
+
+#define CBCLASS DeviceProvider
+START_DISPATCH;
+CB(ADDREF, AddRef)
+CB(RELEASE, Release)
+CB(QUERYINTERFACE, QueryInterface)
+CB(API_BEGINDISCOVERY, BeginDiscovery)
+CB(API_CANCELDISCOVERY, CancelDiscovery)
+CB(API_GETACTIVE, GetActive)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/provider.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/provider.h
new file mode 100644
index 00000000..27afc72a
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/provider.h
@@ -0,0 +1,50 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_IMPLEMENTATION_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_IMPLEMENTATION_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../../devices/ifc_deviceprovider.h"
+#include "./testSuite.h"
+
+class DeviceProvider : public ifc_deviceprovider
+{
+protected:
+ DeviceProvider();
+ ~DeviceProvider();
+public:
+ static HRESULT CreateInstance(DeviceProvider **instance);
+
+public:
+ /* Dispatchable */
+ size_t AddRef();
+ size_t Release();
+ int QueryInterface(GUID interface_guid, void **object);
+
+ /* ifc_deviceprovider */
+ HRESULT BeginDiscovery(api_devicemanager *manager);
+ HRESULT CancelDiscovery();
+ HRESULT GetActive();
+public:
+ HRESULT Register(api_devicemanager *manager);
+ HRESULT Unregister(api_devicemanager *manager);
+
+private:
+ DWORD DiscoveryThread(api_devicemanager *manager);
+ friend static DWORD CALLBACK DeviceProvider_DiscoveryThreadStarter(void *param);
+
+protected:
+ size_t ref;
+ HANDLE discoveryThread;
+ HANDLE cancelEvent;
+ TestSuite testSuite;
+ CRITICAL_SECTION lock;
+
+protected:
+ RECVS_DISPATCH;
+};
+
+
+#endif //_NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_IMPLEMENTATION_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/resource.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/resource.h
new file mode 100644
index 00000000..61e9b42c
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/resource.h
@@ -0,0 +1,39 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by resources.rc
+//
+#define IDS_PLUGIN_NAME 101
+#define IDD_DEVICE_VIEW 102
+#define IDD_ICON_EDITOR 104
+#define IDC_EDIT_NAME 1001
+#define IDC_EDIT_TITLE 1002
+#define IDC_EDIT_TYPE 1003
+#define IDC_EDIT_CONNECTION 1004
+#define IDC_COMBO_ATTACHED 1005
+#define IDC_COMBO_VISIBLE 1006
+#define IDC_EDIT_TOTALSPACE 1007
+#define IDC_SPIN_TOTALSPACE 1008
+#define IDC_EDIT_USEDSPACE 1009
+#define IDC_SPIN_USEDSPACE 1010
+#define IDC_COMBO_ICONS 1011
+#define IDC_BUTTON_NEWICON 1012
+#define IDC_BUTTON_REMOVEICON 1013
+#define IDC_STATIC_PREVIEWICON 1014
+#define IDC_EDIT_WIDTH 1015
+#define IDC_EDIT_HEIGHT 1016
+#define IDC_EDIT_PATH 1017
+#define IDC_BUTTON_BROWSE 1018
+#define IDC_BUTTON1 1019
+#define IDC_BUTTON_EDITICON 1019
+#define IDS_STRING102 65535
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 105
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1020
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/resources.rc b/Src/Plugins/Library/ml_devices/gen_deviceprovider/resources.rc
new file mode 100644
index 00000000..3abf6c71
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/resources.rc
@@ -0,0 +1,186 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "afxres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""afxres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_DEVICE_VIEW DIALOGEX 0, 0, 397, 282
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_NOPARENTNOTIFY | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ EDITTEXT IDC_EDIT_NAME,57,23,101,12,ES_AUTOHSCROLL | ES_READONLY
+ EDITTEXT IDC_EDIT_TYPE,57,36,101,12,ES_AUTOHSCROLL | ES_READONLY
+ EDITTEXT IDC_EDIT_CONNECTION,57,50,101,12,ES_AUTOHSCROLL | ES_READONLY
+ EDITTEXT IDC_EDIT_TITLE,57,70,101,12,ES_AUTOHSCROLL
+ COMBOBOX IDC_COMBO_ATTACHED,57,85,101,30,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
+ COMBOBOX IDC_COMBO_VISIBLE,57,101,101,30,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
+ EDITTEXT IDC_EDIT_TOTALSPACE,57,117,101,16,ES_AUTOHSCROLL | ES_NUMBER
+ CONTROL "",IDC_SPIN_TOTALSPACE,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,149,118,9,12,WS_EX_TRANSPARENT
+ EDITTEXT IDC_EDIT_USEDSPACE,57,134,101,16,ES_AUTOHSCROLL | ES_NUMBER
+ CONTROL "",IDC_SPIN_USEDSPACE,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,149,136,9,12,WS_EX_TRANSPARENT
+ COMBOBOX IDC_COMBO_ICONS,169,34,221,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ PUSHBUTTON "New",IDC_BUTTON_NEWICON,264,49,40,14
+ PUSHBUTTON "Edit",IDC_BUTTON_EDITICON,307,49,40,14
+ PUSHBUTTON "Remove",IDC_BUTTON_REMOVEICON,350,49,40,14
+ CONTROL "",IDC_STATIC_PREVIEWICON,"Static",SS_BITMAP | SS_CENTERIMAGE,169,70,221,170,WS_EX_STATICEDGE
+ LTEXT "Device View",IDC_STATIC,7,7,39,8
+ CONTROL "",IDC_STATIC,"Static",SS_ETCHEDHORZ,7,16,383,1
+ LTEXT "Name:",IDC_STATIC,15,24,22,8
+ LTEXT "Title:",IDC_STATIC,15,71,17,8
+ LTEXT "Type:",IDC_STATIC,15,37,20,8
+ LTEXT "Connection:",IDC_STATIC,15,51,40,8
+ LTEXT "Attached:",IDC_STATIC,15,87,33,8
+ LTEXT "Visible:",IDC_STATIC,15,103,23,8
+ LTEXT "Capacity:",IDC_STATIC,15,119,32,8
+ LTEXT "Used Space:",IDC_STATIC,15,136,41,8
+ LTEXT "Icons:",IDC_STATIC,169,22,21,8
+END
+
+IDD_ICON_EDITOR DIALOGEX 0, 0, 290, 79
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Device Icon"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ DEFPUSHBUTTON "OK",IDOK,180,58,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,233,58,50,14
+ LTEXT "Width:",IDC_STATIC,8,34,35,8
+ LTEXT "Height:",IDC_STATIC,47,33,29,8
+ EDITTEXT IDC_EDIT_WIDTH,7,44,35,14,ES_AUTOHSCROLL | ES_NUMBER
+ EDITTEXT IDC_EDIT_HEIGHT,46,44,35,14,ES_AUTOHSCROLL | ES_NUMBER
+ EDITTEXT IDC_EDIT_PATH,7,18,250,14,ES_AUTOHSCROLL
+ LTEXT "Path:",IDC_STATIC,7,7,18,8
+ PUSHBUTTON "...",IDC_BUTTON_BROWSE,261,18,22,14
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_DEVICE_VIEW, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 390
+ VERTGUIDE, 15
+ VERTGUIDE, 57
+ VERTGUIDE, 158
+ VERTGUIDE, 169
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 275
+ END
+
+ IDD_ICON_EDITOR, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 283
+ VERTGUIDE, 42
+ VERTGUIDE, 46
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 72
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog Info
+//
+
+IDD_DEVICE_VIEW DLGINIT
+BEGIN
+ IDC_COMBO_ATTACHED, 0x403, 4, 0
+0x6559, 0x0073,
+ IDC_COMBO_ATTACHED, 0x403, 3, 0
+0x6f4e, "\000"
+ IDC_COMBO_VISIBLE, 0x403, 4, 0
+0x6559, 0x0073,
+ IDC_COMBO_VISIBLE, 0x403, 3, 0
+0x6f4e, "\000"
+ 0
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_PLUGIN_NAME "Nullsoft Test Device Provider v%d.%d"
+END
+
+STRINGTABLE
+BEGIN
+ 65535 "{51B54A05-B711-4509-80AE-5A0AAA502FA5}"
+END
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/stringBuilder.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/stringBuilder.cpp
new file mode 100644
index 00000000..85596feb
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/stringBuilder.cpp
@@ -0,0 +1,85 @@
+#include "main.h"
+#include "./stringBuilder.h"
+#include <strsafe.h>
+
+StringBuilder::StringBuilder()
+ : buffer(NULL), cursor(NULL), allocated(0), remaining(0)
+{
+}
+
+StringBuilder::~StringBuilder()
+{
+ String_Free(buffer);
+}
+
+HRESULT StringBuilder::Allocate(size_t newSize)
+{
+ if (newSize <= allocated)
+ return S_FALSE;
+
+ LPWSTR t = String_ReAlloc(buffer, newSize);
+ if (NULL == t) return E_OUTOFMEMORY;
+
+ cursor = t + (cursor - buffer);
+ buffer = t;
+
+ remaining += newSize - allocated;
+ allocated = newSize;
+
+ return S_OK;
+}
+
+void StringBuilder::Clear(void)
+{
+ if (NULL != buffer)
+ {
+ buffer[0] = L'\0';
+ }
+ cursor = buffer;
+ remaining = allocated;
+}
+
+LPCWSTR StringBuilder::Get(void)
+{
+ return buffer;
+}
+
+HRESULT StringBuilder::Set(size_t index, WCHAR value)
+{
+ if (NULL == buffer)
+ return E_POINTER;
+
+ if (index >= allocated)
+ return E_INVALIDARG;
+
+ buffer[index] = value;
+ return S_OK;
+}
+
+HRESULT StringBuilder::Append(LPCWSTR pszString)
+{
+ HRESULT hr;
+ if (NULL == buffer)
+ {
+ hr = Allocate(1024);
+ if (FAILED(hr)) return hr;
+ }
+
+ size_t cchCursor = remaining;
+ hr = StringCchCopyEx(cursor, cchCursor, pszString, &cursor, &remaining, STRSAFE_IGNORE_NULLS);
+ if (STRSAFE_E_INSUFFICIENT_BUFFER == hr)
+ {
+ size_t offset = cchCursor - remaining;
+ size_t requested = lstrlen(pszString) + (allocated - remaining) + 1;
+ size_t newsize = allocated * 2;
+ while (newsize < requested) newsize = newsize * 2;
+
+ hr = Allocate(newsize);
+ if (FAILED(hr)) return hr;
+
+ hr = StringCchCopyEx(cursor, remaining, pszString + offset, &cursor, &remaining, STRSAFE_IGNORE_NULLS);
+ }
+
+ return hr;
+}
+ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/stringBuilder.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/stringBuilder.h
new file mode 100644
index 00000000..cc2170b7
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/stringBuilder.h
@@ -0,0 +1,31 @@
+#ifndef NULLSOFT_WINAMP_STRING_BUILDER_HEADER
+#define NULLSOFT_WINAMP_STRING_BUILDER_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+class StringBuilder
+{
+public:
+ StringBuilder();
+ ~StringBuilder();
+
+public:
+ HRESULT Allocate(size_t newSize);
+ void Clear(void);
+ LPCWSTR Get(void);
+ HRESULT Set(size_t index, WCHAR value);
+ HRESULT Append(LPCWSTR pszString);
+
+
+protected:
+ LPWSTR buffer;
+ LPWSTR cursor;
+ size_t allocated;
+ size_t remaining;
+};
+
+#endif //NULLSOFT_WINAMP_STRING_BUILDER_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/strings.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/strings.cpp
new file mode 100644
index 00000000..aae4c53f
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/strings.cpp
@@ -0,0 +1,243 @@
+#include "main.h"
+#include "./strings.h"
+#include <strsafe.h>
+
+
+wchar_t *
+String_Malloc(size_t size)
+{
+ return (wchar_t *)malloc(sizeof(wchar_t) * size);
+}
+
+wchar_t *
+String_ReAlloc(wchar_t *string, size_t size)
+{
+ return (wchar_t *)realloc(string, sizeof(wchar_t) * size);
+}
+
+void
+String_Free(wchar_t *string)
+{
+ if (NULL != string)
+ free(string);
+}
+
+wchar_t *
+String_Duplicate(const wchar_t *string)
+{
+ int length;
+ wchar_t *copy;
+
+ if (NULL == string)
+ return NULL;
+
+ length = lstrlenW(string) + 1;
+
+ copy = String_Malloc(length);
+ if (NULL != copy)
+ CopyMemory(copy, string, sizeof(wchar_t) * length);
+
+ return copy;
+}
+
+
+char *
+String_ToAnsi(unsigned int codePage, unsigned long flags, const wchar_t *string,
+ int stringLength, const char *defaultChar, BOOL *usedDefaultChar)
+{
+ char *buffer;
+ int bufferSize;
+
+ if (stringLength < 0)
+ stringLength = lstrlen(string);
+
+ bufferSize = WideCharToMultiByte(codePage, flags, string, stringLength,
+ NULL, 0, defaultChar, usedDefaultChar);
+ if (0 == bufferSize)
+ return NULL;
+
+ buffer = AnsiString_Malloc(bufferSize + 1);
+ if (NULL == buffer)
+ return NULL;
+
+ bufferSize = WideCharToMultiByte(codePage, flags, string, stringLength,
+ buffer, bufferSize, defaultChar, usedDefaultChar);
+ if (0 == bufferSize)
+ {
+ AnsiString_Free(buffer);
+ return NULL;
+ }
+ buffer[bufferSize] = '\0';
+ return buffer;
+}
+
+size_t
+String_CopyTo(wchar_t *destination, const wchar_t *source, size_t size)
+{
+ size_t remaining;
+ if (FAILED(StringCchCopyExW(destination, size, source, NULL, &remaining, STRSAFE_IGNORE_NULLS)))
+ return 0;
+
+ return (size - remaining);
+}
+
+wchar_t *
+String_FromWindowEx(HWND hwnd, size_t *lengthOut, BOOL *errorOut)
+{
+ BOOL error;
+ size_t length;
+ wchar_t *string;
+
+ error = TRUE;
+ string = NULL;
+ length = 0;
+
+ if (NULL != hwnd)
+ {
+ length = GetWindowTextLength(hwnd);
+ if (0 != length ||
+ ERROR_SUCCESS == GetLastError())
+ {
+ string = String_Malloc(length + 1);
+ if(NULL != string)
+ {
+ if (0 == length)
+ {
+ string[0] = L'\0';
+ error = FALSE;
+ }
+ else
+ {
+ length = GetWindowText(hwnd, string, (int)length + 1);
+ if (0 == length && ERROR_SUCCESS != GetLastError())
+ {
+ String_Free(string);
+ string = NULL;
+ }
+ else
+ error = FALSE;
+ }
+ }
+ }
+ }
+
+ if (NULL != lengthOut)
+ *lengthOut = length;
+
+ if (NULL != errorOut)
+ *errorOut = error;
+
+ return string;
+}
+
+char *
+AnsiString_Malloc(size_t size)
+{
+ return (char*)malloc(sizeof(char) * size);
+}
+
+char *
+AnsiString_ReAlloc(char *string, size_t size)
+{
+ return (char*)realloc(string, sizeof(char) * size);
+}
+
+void
+AnsiString_Free(char *string)
+{
+ if (NULL != string)
+ free(string);
+}
+
+char *
+AnsiString_Duplicate(const char *string)
+{
+ char *copy;
+ INT length;
+
+ if (NULL == string)
+ return NULL;
+
+ length = lstrlenA(string) + 1;
+
+ copy = AnsiString_Malloc(length);
+ if (NULL != copy)
+ CopyMemory(copy, string, sizeof(char) * length);
+
+ return copy;
+}
+
+
+wchar_t *
+AnsiString_ToUnicode(unsigned int codePage, unsigned long flags, const char* string, INT stringLength)
+{
+ wchar_t *buffer;
+ int buffferSize;
+
+ if (NULL == string)
+ return NULL;
+
+ buffferSize = MultiByteToWideChar(codePage, flags, string, stringLength, NULL, 0);
+ if (0 == buffferSize)
+ return NULL;
+
+ if (stringLength > 0)
+ buffferSize++;
+
+ buffer = String_Malloc(buffferSize);
+ if (NULL == buffer)
+ return NULL;
+
+ if (0 == MultiByteToWideChar(codePage, flags, string, stringLength, buffer, buffferSize))
+ {
+ String_Free(buffer);
+ return NULL;
+ }
+
+ if (stringLength > 0)
+ buffer[buffferSize - 1] = L'\0';
+
+ return buffer;
+}
+
+wchar_t*
+ResourceString_Duplicate(const wchar_t *source)
+{
+ return (FALSE != IS_INTRESOURCE(source)) ?
+ (LPWSTR)source :
+ String_Duplicate(source);
+}
+
+void
+ResourceString_Free(wchar_t *string)
+{
+ if (FALSE == IS_INTRESOURCE(string))
+ String_Free(string);
+}
+
+size_t
+ResourceString_CopyTo(wchar_t *destination, const wchar_t *source, size_t size)
+{
+ if (NULL == destination)
+ return 0;
+
+ if (NULL == source)
+ {
+ destination[0] = L'\0';
+ return 0;
+ }
+
+ if (FALSE != IS_INTRESOURCE(source))
+ {
+ if (NULL == WASABI_API_LNG)
+ {
+ destination[0] = L'\0';
+ return 0;
+ }
+
+ WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)source, destination, size);
+ return lstrlenW(destination);
+ }
+
+ return String_CopyTo(destination, source, size);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/strings.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/strings.h
new file mode 100644
index 00000000..c2c11baf
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/strings.h
@@ -0,0 +1,82 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_STRINGS_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_STRINGS_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#define IS_STRING_EMPTY(_string) (NULL == (_string) || L'\0' == *(_string))
+
+wchar_t *
+String_Malloc(size_t size);
+
+wchar_t *
+String_ReAlloc(wchar_t *string,
+ size_t size);
+
+void
+String_Free(wchar_t *string);
+
+wchar_t *
+String_Duplicate(const wchar_t *string);
+
+char *
+String_ToAnsi(unsigned int codePage,
+ unsigned long flags,
+ const wchar_t *string,
+ int stringLength,
+ const char *defaultChar,
+ BOOL *usedDefaultChar);
+
+size_t
+String_CopyTo(wchar_t *destination,
+ const wchar_t *source,
+ size_t size);
+
+wchar_t *
+String_FromWindowEx(HWND hwnd,
+ size_t *lengthOut,
+ BOOL *errorOut);
+
+#define String_FromWindow(/*HWND*/ _hwnd)\
+ String_FromWindowEx((_hwnd), NULL, NULL)
+
+/*
+ Ansi String
+*/
+
+char *
+AnsiString_Malloc(size_t size);
+
+char *
+AnsiString_ReAlloc(char *string,
+ size_t size);
+
+void
+AnsiString_Free(char *string);
+
+char *
+AnsiString_Duplicate(const char *string);
+
+wchar_t *
+AnsiString_ToUnicode(unsigned int codePage,
+ unsigned long flags,
+ const char *string,
+ int stringLength);
+
+/*
+ Resource String
+*/
+
+wchar_t*
+ResourceString_Duplicate(const wchar_t *source);
+
+void
+ResourceString_Free(wchar_t *string);
+
+size_t
+ResourceString_CopyTo(wchar_t *destination,
+ const wchar_t *source,
+ size_t size);
+
+#endif //_NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_STRINGS_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/supportedCommand.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/supportedCommand.cpp
new file mode 100644
index 00000000..24966cf0
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/supportedCommand.cpp
@@ -0,0 +1,108 @@
+#include "main.h"
+#include "./supportedCommand.h"
+
+#include <strsafe.h>
+
+DeviceSupportedCommand::DeviceSupportedCommand()
+ : ref(1), name(NULL), flags(DeviceCommandFlag_None)
+{
+}
+
+DeviceSupportedCommand::~DeviceSupportedCommand()
+{
+ AnsiString_Free(name);
+}
+
+HRESULT DeviceSupportedCommand::CreateInstance(const char *name, DeviceSupportedCommand **instance)
+{
+ DeviceSupportedCommand *self;
+
+ if (NULL == instance)
+ return E_POINTER;
+
+ *instance = NULL;
+
+ self = new DeviceSupportedCommand();
+ if (NULL == self)
+ return E_OUTOFMEMORY;
+
+ self->name = AnsiString_Duplicate(name);
+
+ *instance = self;
+ return S_OK;
+}
+
+size_t DeviceSupportedCommand::AddRef()
+{
+ return InterlockedIncrement((LONG*)&ref);
+}
+
+size_t DeviceSupportedCommand::Release()
+{
+ if (0 == ref)
+ return ref;
+
+ LONG r = InterlockedDecrement((LONG*)&ref);
+ if (0 == r)
+ delete(this);
+
+ return r;
+}
+
+int DeviceSupportedCommand::QueryInterface(GUID interface_guid, void **object)
+{
+ if (NULL == object)
+ return E_POINTER;
+
+ if (IsEqualIID(interface_guid, IFC_DeviceSupportedCommand))
+ *object = static_cast<ifc_devicesupportedcommand*>(this);
+ else
+ {
+ *object = NULL;
+ return E_NOINTERFACE;
+ }
+
+ if (NULL == *object)
+ return E_UNEXPECTED;
+
+ AddRef();
+ return S_OK;
+}
+
+const char *DeviceSupportedCommand::GetName()
+{
+ return name;
+}
+
+HRESULT DeviceSupportedCommand::GetFlags(DeviceCommandFlags *flagsOut)
+{
+ if (NULL == flagsOut)
+ return E_POINTER;
+
+ *flagsOut = flags;
+
+ return S_OK;
+}
+
+HRESULT DeviceSupportedCommand::SetFlags(DeviceCommandFlags mask, DeviceCommandFlags value)
+{
+ DeviceCommandFlags temp;
+ temp = (flags & mask) | (mask & value);
+
+ if (temp == flags)
+ return S_FALSE;
+
+ flags = temp;
+
+ return S_OK;
+}
+
+#define CBCLASS DeviceSupportedCommand
+START_DISPATCH;
+CB(ADDREF, AddRef)
+CB(RELEASE, Release)
+CB(QUERYINTERFACE, QueryInterface)
+CB(API_GETNAME, GetName)
+CB(API_GETFLAGS, GetFlags)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/supportedCommand.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/supportedCommand.h
new file mode 100644
index 00000000..6cdc2700
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/supportedCommand.h
@@ -0,0 +1,46 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_SUPPORTED_COMMAND_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_SUPPORTED_COMMAND_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include <ifc_devicesupportedcommand.h>
+
+
+class DeviceSupportedCommand : public ifc_devicesupportedcommand
+{
+protected:
+ DeviceSupportedCommand();
+ ~DeviceSupportedCommand();
+
+public:
+ static HRESULT CreateInstance(const char *name,
+ DeviceSupportedCommand **instance);
+
+public:
+ /* Dispatchable */
+ size_t AddRef();
+ size_t Release();
+ int QueryInterface(GUID interface_guid, void **object);
+
+ /* ifc_devicesupportedcommand */
+ const char *GetName();
+ HRESULT GetFlags(DeviceCommandFlags *flags);
+
+public:
+ HRESULT SetFlags(DeviceCommandFlags mask, DeviceCommandFlags value);
+
+protected:
+ size_t ref;
+ char *name;
+ DeviceCommandFlags flags;
+
+
+protected:
+ RECVS_DISPATCH;
+};
+
+
+#endif //_NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_SUPPORTED_COMMAND_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/testSuite.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/testSuite.cpp
new file mode 100644
index 00000000..c5821925
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/testSuite.cpp
@@ -0,0 +1,555 @@
+#include "main.h"
+#include "./testSuite.h"
+
+#include <strsafe.h>
+
+TestSuite::TestSuite()
+{
+}
+
+TestSuite::~TestSuite()
+{
+ size_t index;
+
+ index = deviceList.size();
+ while(index--)
+ {
+ deviceList[index]->Release();
+ }
+
+ index = typeList.size();
+ while(index--)
+ {
+ typeList[index]->Release();
+ }
+
+ index = connectionList.size();
+ while(index--)
+ {
+ connectionList[index]->Release();
+ }
+
+ index = commandList.size();
+ while(index--)
+ {
+ commandList[index]->Release();
+ }
+
+ index = insertList.size();
+ while(index--)
+ {
+ AnsiString_Free(insertList[index]);
+ }
+}
+
+BOOL TestSuite::AddDevice(Device *device)
+{
+ if (NULL == device)
+ return FALSE;
+
+ deviceList.push_back(device);
+ device->AddRef();
+
+ return TRUE;
+}
+
+size_t TestSuite::GetDeviceCount()
+{
+ return deviceList.size();
+}
+
+Device *TestSuite::GetDevice(size_t index)
+{
+ return deviceList[index];
+}
+
+static int
+String_RemoveCounter(wchar_t *string)
+{
+ int len;
+
+ len = lstrlenW(string);
+ if (len > 3)
+ {
+ int cutoff = 0;
+ if (string[len-1] == L')')
+ {
+ WORD charType;
+ cutoff = 1;
+ while(len > cutoff &&
+ FALSE != GetStringTypeW(CT_CTYPE1, string + (len - 1) - cutoff, 1, &charType) &&
+ 0 != (C1_DIGIT & charType))
+ {
+ cutoff++;
+ }
+
+ if (len > cutoff &&
+ cutoff > 1 &&
+ L'(' == string[len - 1 - cutoff])
+ {
+ cutoff++;
+ if (len > cutoff &&
+ L' ' == string[len - 1 - cutoff])
+ {
+ string[len - 1 - cutoff] = L'\0';
+ len -= (cutoff + 1);
+ }
+ }
+ }
+ }
+
+ return len;
+}
+
+
+static int
+AnsiString_RemoveCounter(char *string)
+{
+ int len;
+
+ len = lstrlenA(string);
+ if (len > 3)
+ {
+ int cutoff = 0;
+ if (string[len-1] == ')')
+ {
+ WORD charType;
+ cutoff = 1;
+ while(len > cutoff &&
+ FALSE != GetStringTypeA(LOCALE_SYSTEM_DEFAULT, CT_CTYPE1, string + (len - 1) - cutoff, 1, &charType) &&
+ 0 != (C1_DIGIT & charType))
+ {
+ cutoff++;
+ }
+
+ if (len > cutoff &&
+ cutoff > 1 &&
+ '(' == string[len - 1 - cutoff])
+ {
+ cutoff++;
+ if (len > cutoff &&
+ ' ' == string[len - 1 - cutoff])
+ {
+ string[len - 1 - cutoff] = '\0';
+ len -= (cutoff + 1);
+ }
+ }
+ }
+ }
+
+ return len;
+}
+
+Device *TestSuite::CreateDeviceCopy(Device *source)
+{
+ const char *name;
+ size_t index, counter;
+ char buffer[1024];
+ wchar_t *displayName;
+ BOOL found;
+ Device *destination;
+ int length;
+
+ if (NULL == source)
+ return NULL;
+
+ found = FALSE;
+ counter = 0;
+
+ name = source->GetName();
+ StringCbCopyA(buffer, sizeof(buffer), name);
+ length = AnsiString_RemoveCounter(buffer);
+
+ for (;;)
+ {
+
+ if (0 != counter)
+ StringCbPrintfA(buffer + length, sizeof(buffer) - length, " (%d)", counter);
+
+ found = TRUE;
+
+ index = deviceList.size();
+ while(index--)
+ {
+ if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, 0, deviceList[index]->GetName(), -1, buffer, -1))
+ {
+ found = FALSE;
+ break;
+ }
+ }
+
+ if (FALSE != found)
+ break;
+ else
+ counter++;
+ }
+
+ if (FAILED(Device::CreateInstance(buffer, source->GetType(), source->GetConnection(), &destination)))
+ return NULL;
+
+ source->CopyTo(destination);
+
+ source->GetDisplayName((wchar_t*)buffer, sizeof(buffer)/sizeof(wchar_t));
+ displayName = (wchar_t*)buffer;
+ length = String_RemoveCounter(displayName);
+
+ if (0 != counter)
+ StringCchPrintf(displayName + length, sizeof(buffer)/sizeof(wchar_t) - length, L" (%d)", counter);
+
+ destination->SetDisplayName(displayName);
+
+
+ return destination;
+
+}
+Device *TestSuite::GetRandomDevice()
+{
+ size_t index;
+ Device *device;
+ LARGE_INTEGER perfCounter;
+
+ if (0 == deviceList.size())
+ return NULL;
+
+ if (FALSE != QueryPerformanceCounter(&perfCounter))
+ srand(perfCounter.LowPart);
+ else
+ srand(GetTickCount());
+
+ index = (size_t)((double)rand()/(RAND_MAX + 1) * deviceList.size());
+ device = deviceList[index];
+
+ if (S_OK == device->IsConnected())
+ {
+ size_t search;
+
+ for(search = index + 1; search < deviceList.size(); search++)
+ {
+ device = deviceList[search];
+ if (S_FALSE == device->IsConnected())
+ return device;
+ }
+
+ search = index;
+ while(search--)
+ {
+ device = deviceList[search];
+ if (S_FALSE == device->IsConnected())
+ return device;
+ }
+
+ device = CreateDeviceCopy(deviceList[index]);
+ if (NULL != device)
+ {
+ size_t totalSpace, usedSpace;
+ totalSpace = (size_t)((double)rand()/(RAND_MAX + 1) * 1000);
+ usedSpace = (size_t)((double)rand()/(RAND_MAX + 1) * totalSpace);
+
+ device->SetTotalSpace(totalSpace);
+ device->SetUsedSpace(usedSpace);
+
+ device->Disconnect();
+ device->Detach(NULL);
+ AddDevice(device);
+ }
+ }
+
+ return device;
+}
+
+BOOL TestSuite::AddType(ifc_devicetype *type)
+{
+ if (NULL == type)
+ return FALSE;
+
+ typeList.push_back(type);
+ type->AddRef();
+
+ return TRUE;
+}
+
+size_t TestSuite::GetTypeCount()
+{
+ return typeList.size();
+}
+
+ifc_devicetype *TestSuite::GetType(size_t index)
+{
+ return typeList[index];
+}
+
+
+BOOL TestSuite::RegisterTypes(api_devicemanager *manager)
+{
+ if (NULL == manager)
+ return FALSE;
+
+ if (0 != typeList.size())
+ manager->TypeRegister((ifc_devicetype**)typeList.begin(), typeList.size());
+
+ return TRUE;
+}
+
+BOOL TestSuite::UnregisterTypes(api_devicemanager *manager)
+{
+ size_t index;
+ if (NULL == manager)
+ return FALSE;
+
+ index = typeList.size();
+ while(index--)
+ {
+ manager->TypeUnregister(typeList[index]->GetName());
+ }
+
+ return TRUE;
+}
+
+BOOL TestSuite::SetIconBase(const wchar_t *path)
+{
+ size_t index;
+ Device *device;
+ ifc_devicetype *type;
+ ifc_devicetypeeditor *typeEditor;
+ ifc_deviceiconstore *iconStore;
+ ifc_deviceconnection *connection;
+ ifc_deviceconnectioneditor *connectionEditor;
+ ifc_devicecommand *command;
+ ifc_devicecommandeditor *commandEditor;
+
+ index = deviceList.size();
+ while(index--)
+ {
+ device = deviceList[index];
+ device->SetIconBase(path);
+ }
+
+ index = typeList.size();
+ while(index--)
+ {
+ type = typeList[index];
+ if (SUCCEEDED(type->QueryInterface(IFC_DeviceTypeEditor, (void**)&typeEditor)))
+ {
+ if(SUCCEEDED(typeEditor->GetIconStore(&iconStore)))
+ {
+ iconStore->SetBasePath(path);
+ iconStore->Release();
+ }
+ typeEditor->Release();
+ }
+ }
+
+ index = commandList.size();
+ while(index--)
+ {
+ command = commandList[index];
+ if (SUCCEEDED(command->QueryInterface(IFC_DeviceCommandEditor, (void**)&commandEditor)))
+ {
+ if(SUCCEEDED(commandEditor->GetIconStore(&iconStore)))
+ {
+ iconStore->SetBasePath(path);
+ iconStore->Release();
+ }
+ commandEditor->Release();
+ }
+ }
+
+ index = connectionList.size();
+ while(index--)
+ {
+ connection = connectionList[index];
+ if (SUCCEEDED(connection->QueryInterface(IFC_DeviceConnectionEditor, (void**)&connectionEditor)))
+ {
+ if(SUCCEEDED(connectionEditor->GetIconStore(&iconStore)))
+ {
+ iconStore->SetBasePath(path);
+ iconStore->Release();
+ }
+ connectionEditor->Release();
+ }
+ }
+
+
+ return S_OK;
+}
+
+BOOL TestSuite::SetConnectList(char **devices, size_t count)
+{
+ size_t index;
+ char *name;
+
+ index = insertList.size();
+ if (index > 0)
+ {
+ while(index--)
+ {
+ name = insertList[index];
+ AnsiString_Free(name);
+ }
+ insertList.clear();
+ }
+
+ for(index = 0; index < count; index++)
+ {
+ name = AnsiString_Duplicate(devices[index]);
+ if (NULL != name)
+ insertList.push_back(name);
+ }
+
+ return TRUE;
+}
+
+Device *TestSuite::GetDeviceByName(const char *name)
+{
+ size_t index;
+ Device *device;
+
+ for (index = 0; index < deviceList.size(); index++)
+ {
+ device = deviceList[index];
+ if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, NORM_IGNORECASE, name, -1, device->GetName(), -1))
+ return device;
+ }
+
+ return NULL;
+}
+
+BOOL TestSuite::RegisterDevices(api_devicemanager *manager)
+{
+ size_t index;
+ const char *name;
+ Device *device;
+
+ if (NULL == manager)
+ return FALSE;
+
+ for (index = 0; index < insertList.size(); index++)
+ {
+ name = insertList[index];
+ device = GetDeviceByName(name);
+ if (NULL != device)
+ {
+ manager->DeviceRegister((ifc_device**)&device, 1);
+ device->Connect();
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL TestSuite::UnregisterDevices(api_devicemanager *manager)
+{
+ size_t index;
+ Device *device;
+
+ if (NULL == manager)
+ return FALSE;
+
+ index = deviceList.size();
+ while(index--)
+ {
+ device = deviceList[index];
+ if (S_OK == device->IsConnected())
+ {
+ device->Disconnect();
+ manager->DeviceUnregister(device->GetName());
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL TestSuite::AddConnection(ifc_deviceconnection *connection)
+{
+ if (NULL == connection)
+ return FALSE;
+
+ connectionList.push_back(connection);
+ connection->AddRef();
+
+ return TRUE;
+}
+
+size_t TestSuite::GetConnectionCount()
+{
+ return connectionList.size();
+}
+
+ifc_deviceconnection *TestSuite::GetConnection(size_t index)
+{
+ return connectionList[index];
+}
+
+BOOL TestSuite::RegisterConnections(api_devicemanager *manager)
+{
+ if (NULL == manager)
+ return FALSE;
+
+ if (0 != connectionList.size())
+ manager->ConnectionRegister((ifc_deviceconnection**)connectionList.begin(), connectionList.size());
+
+ return TRUE;
+}
+
+BOOL TestSuite::UnregisterConnections(api_devicemanager *manager)
+{
+ size_t index;
+ if (NULL == manager)
+ return FALSE;
+
+ index = connectionList.size();
+ while(index--)
+ {
+ manager->ConnectionUnregister(connectionList[index]->GetName());
+ }
+
+ return TRUE;
+}
+
+
+BOOL TestSuite::AddCommand(ifc_devicecommand *command)
+{
+ if (NULL == command)
+ return FALSE;
+
+ commandList.push_back(command);
+ command->AddRef();
+
+ return TRUE;
+}
+
+size_t TestSuite::GetCommandCount()
+{
+ return commandList.size();
+}
+
+ifc_devicecommand *TestSuite::GetCommand(size_t index)
+{
+ return commandList[index];
+}
+
+BOOL TestSuite::RegisterCommands(api_devicemanager *manager)
+{
+ if (NULL == manager)
+ return FALSE;
+
+ if (0 != commandList.size())
+ manager->CommandRegister((ifc_devicecommand**)commandList.begin(), commandList.size());
+
+ return TRUE;
+}
+
+BOOL TestSuite::UnregisterCommands(api_devicemanager *manager)
+{
+ size_t index;
+ if (NULL == manager)
+ return FALSE;
+
+ index = commandList.size();
+ while(index--)
+ {
+ manager->CommandUnregister(commandList[index]->GetName());
+ }
+
+ return TRUE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/testSuite.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/testSuite.h
new file mode 100644
index 00000000..596f9731
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/testSuite.h
@@ -0,0 +1,67 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_TEST_SUITE_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_TEST_SUITE_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include <vector>
+#include "./device.h"
+
+class TestSuite
+{
+public:
+ TestSuite();
+ ~TestSuite();
+
+public:
+ BOOL AddDevice(Device *device);
+ size_t GetDeviceCount();
+ Device *GetDevice(size_t index);
+ Device *GetRandomDevice();
+ Device *CreateDeviceCopy(Device *source);
+ Device *GetDeviceByName(const char *name);
+ BOOL RegisterDevices(api_devicemanager *manager);
+ BOOL UnregisterDevices(api_devicemanager *manager);
+
+ BOOL AddType(ifc_devicetype *type);
+ size_t GetTypeCount();
+ ifc_devicetype *GetType(size_t index);
+ BOOL RegisterTypes(api_devicemanager *manager);
+ BOOL UnregisterTypes(api_devicemanager *manager);
+
+ BOOL AddConnection(ifc_deviceconnection *connection);
+ size_t GetConnectionCount();
+ ifc_deviceconnection *GetConnection(size_t index);
+ BOOL RegisterConnections(api_devicemanager *manager);
+ BOOL UnregisterConnections(api_devicemanager *manager);
+
+ BOOL AddCommand(ifc_devicecommand *command);
+ size_t GetCommandCount();
+ ifc_devicecommand *GetCommand(size_t index);
+ BOOL RegisterCommands(api_devicemanager *manager);
+ BOOL UnregisterCommands(api_devicemanager *manager);
+
+ BOOL SetIconBase(const wchar_t *path);
+ BOOL SetConnectList(char **devices, size_t count);
+
+private:
+ typedef std::vector<Device*> DeviceList;
+ typedef std::vector<ifc_devicetype*> TypeList;
+ typedef std::vector<ifc_deviceconnection*> ConnectionList;
+ typedef std::vector<ifc_devicecommand*> CommandList;
+
+ typedef std::vector<char*> NameList;
+
+private:
+ DeviceList deviceList;
+ TypeList typeList;
+ ConnectionList connectionList;
+ CommandList commandList;
+ NameList insertList;
+
+
+};
+
+#endif // _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_TEST_SUITE_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/testSuiteLoader.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/testSuiteLoader.cpp
new file mode 100644
index 00000000..0496e909
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/testSuiteLoader.cpp
@@ -0,0 +1,226 @@
+#include "main.h"
+#include "./testSuiteLoader.h"
+
+#include "../../xml/obj_xml.h"
+#include <api/service/waservicefactory.h>
+
+#include <strsafe.h>
+
+
+typedef void (CALLBACK *LOADERTAGCALLBACK)(TestSuiteLoader* /*loader*/, const wchar_t* /*value*/);
+
+typedef struct LOADERTAG
+{
+ const wchar_t *name;
+ LOADERTAGCALLBACK callback;
+} LOADERTAG;
+
+static void CALLBACK
+LoaderTag_ImageBase(TestSuiteLoader *loader, const wchar_t *value)
+{
+ String_Free(loader->imageBase);
+ loader->imageBase = String_Duplicate(value);
+}
+
+static void CALLBACK
+LoaderTag_Connect(TestSuiteLoader *loader, const wchar_t *value)
+{
+ if (IS_STRING_EMPTY(value))
+ return;
+
+ const wchar_t *block, *cursor;
+ char *name;
+ size_t length;
+
+ block = value;
+ cursor = block;
+ for(;;)
+ {
+ if (L'\0' == *cursor ||
+ L';' == *cursor ||
+ L',' == *cursor)
+ {
+
+ if (block < cursor)
+ {
+ length = cursor - block;
+ name = String_ToAnsi(CP_UTF8, 0, block, (int)length, NULL, NULL);
+ if (NULL != name)
+ loader->connectList.push_back(name);
+ }
+
+ if (L'\0' == *cursor)
+ break;
+
+ block = cursor + 1;
+ }
+ cursor++;
+ }
+ //loader->SetImageBase(value);
+}
+
+
+static const LOADERTAG knownTags[LOADER_TAG_MAX] =
+{
+ {L"imageBase", LoaderTag_ImageBase},
+ {L"connect", LoaderTag_Connect},
+};
+
+TestSuiteLoader::TestSuiteLoader()
+ : imageBase(NULL)
+{
+}
+
+TestSuiteLoader::~TestSuiteLoader()
+{
+ size_t index;
+
+ index = connectList.size();
+ while(index--)
+ {
+ AnsiString_Free(connectList[index]);
+ }
+
+ String_Free(imageBase);
+
+}
+
+BOOL TestSuiteLoader::Load(const wchar_t *path, TestSuite *testSuite)
+{
+ BOOL result;
+ HANDLE fileHandle;
+ obj_xml *reader;
+
+ if (NULL == testSuite)
+ return FALSE;
+
+ if (NULL == path || L'\0' == *path)
+ return FALSE;
+
+ fileHandle = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
+ if (INVALID_HANDLE_VALUE == fileHandle)
+ return FALSE;
+
+ result = FALSE;
+ if (NULL != WASABI_API_SVC)
+ {
+ waServiceFactory *sf = WASABI_API_SVC->service_getServiceByGuid(obj_xmlGUID);
+ reader = (NULL != sf) ? (obj_xml*)sf->getInterface() : NULL;
+ if (NULL != reader)
+ {
+ if (OBJ_XML_SUCCESS == reader->xmlreader_open())
+ {
+ reader->xmlreader_registerCallback(L"testprovider\fimageBase", this);
+ reader->xmlreader_registerCallback(L"testprovider\fconnect", this);
+ ZeroMemory(hitList, sizeof(hitList));
+
+ deviceParser.Begin(reader, testSuite);
+ typeParser.Begin(reader, testSuite);
+ connectionParser.Begin(reader, testSuite);
+ commandParser.Begin(reader, testSuite);
+
+ result = FeedFile(reader, fileHandle, 8192);
+
+ deviceParser.End();
+ typeParser.End();
+ connectionParser.End();
+ commandParser.End();
+
+ reader->xmlreader_close();
+
+ testSuite->SetIconBase(imageBase);
+ testSuite->SetConnectList(connectList.begin(), connectList.size());
+
+ }
+ sf->releaseInterface(reader);
+ }
+ }
+
+ CloseHandle(fileHandle);
+
+ return result;
+}
+
+BOOL TestSuiteLoader::FeedFile(obj_xml *reader, HANDLE fileHandle, DWORD bufferSize)
+{
+ BOOL result;
+ DWORD read;
+ BYTE *buffer;
+ int readerCode;
+
+ if (NULL == reader ||
+ INVALID_HANDLE_VALUE == fileHandle ||
+ 0 == bufferSize)
+ {
+ return FALSE;
+ }
+
+ buffer = (BYTE*)malloc(bufferSize);
+ if (NULL == buffer)
+ return FALSE;
+
+
+ readerCode = OBJ_XML_SUCCESS;
+ result = TRUE;
+
+ for(;;)
+ {
+ if (FALSE == ReadFile(fileHandle, buffer, bufferSize, &read, NULL) || 0 == read)
+ {
+ result = FALSE;
+
+ if (0 == read && OBJ_XML_SUCCESS == readerCode)
+ reader->xmlreader_feed(0, 0);
+
+ break;
+ }
+
+ readerCode = reader->xmlreader_feed(buffer, read);
+ if (OBJ_XML_SUCCESS != readerCode)
+ {
+ result = FALSE;
+ break;
+ }
+ }
+
+ free(buffer);
+ return result;
+}
+
+void TestSuiteLoader::Event_XmlStartElement(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params)
+{
+ elementString.Clear();
+}
+
+void TestSuiteLoader::Event_XmlEndElement(const wchar_t *xmlpath, const wchar_t *xmltag)
+{
+ for (size_t i = 0; i < LOADER_TAG_MAX; i++)
+ {
+ if (FALSE == hitList[i] &&
+ CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, knownTags[i].name, -1, xmltag, -1))
+ {
+ knownTags[i].callback(this, elementString.Get());
+ hitList[i] = TRUE;
+ break;
+ }
+ }
+}
+
+void TestSuiteLoader::Event_XmlCharData(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *value)
+{
+ elementString.Append(value);
+}
+
+void TestSuiteLoader::Event_XmlError(int linenum, int errcode, const wchar_t *errstr)
+{
+ elementString.Clear();
+}
+
+#define CBCLASS TestSuiteLoader
+START_DISPATCH;
+VCB(ONSTARTELEMENT, Event_XmlStartElement)
+VCB(ONENDELEMENT, Event_XmlEndElement)
+VCB(ONCHARDATA, Event_XmlCharData)
+VCB(ONERROR, Event_XmlError)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/testSuiteLoader.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/testSuiteLoader.h
new file mode 100644
index 00000000..1bfcb43e
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/testSuiteLoader.h
@@ -0,0 +1,60 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_TEST_SUITE_LOADER_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_TEST_SUITE_LOADER_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../../xml/ifc_xmlreadercallback.h"
+#include <vector>
+#include "./DeviceNodeParser.h"
+#include "./DeviceTypeNodeParser.h"
+#include "./DeviceConnectionNodeParser.h"
+#include "./DeviceCommandNodeParser.h"
+
+class obj_xml;
+
+#define LOADER_TAG_MAX 2
+
+class TestSuiteLoader : public ifc_xmlreadercallback
+{
+
+public:
+ TestSuiteLoader();
+ ~TestSuiteLoader();
+
+public:
+ BOOL Load(const wchar_t *path, TestSuite *testSuite);
+
+private:
+ BOOL FeedFile(obj_xml *reader, HANDLE hFile, DWORD bufferSize);
+
+ void Event_XmlStartElement(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params);
+ void Event_XmlEndElement(const wchar_t *xmlpath, const wchar_t *xmltag);
+ void Event_XmlCharData(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *value);
+ void Event_XmlError(int linenum, int errcode, const wchar_t *errstr);
+
+protected:
+ friend static void CALLBACK LoaderTag_ImageBase(TestSuiteLoader *loader, const wchar_t *value);
+ friend static void CALLBACK LoaderTag_Connect(TestSuiteLoader *loader, const wchar_t *value);
+protected:
+ typedef std::vector<char*> NameList;
+
+protected:
+ StringBuilder elementString;
+ DeviceNodeParser deviceParser;
+ DeviceTypeNodeParser typeParser;
+ DeviceConnectionNodeParser connectionParser;
+ DeviceCommandNodeParser commandParser;
+ BOOL hitList[LOADER_TAG_MAX];
+
+ wchar_t *imageBase;
+
+ NameList connectList;
+
+protected:
+ RECVS_DISPATCH;
+};
+
+#endif // _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_TEST_SUITE_LOADER_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/testprovider.xml b/Src/Plugins/Library/ml_devices/gen_deviceprovider/testprovider.xml
new file mode 100644
index 00000000..f5213363
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/testprovider.xml
@@ -0,0 +1,244 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<testprovider>
+ <imageBase>c:\winamp\ml_devices\resources\food</imageBase>
+ <connect>iconTest;apple;banana;grape;pear;pepper;eggplant;cucumber;carrot</connect>
+ <connections>
+ <connection name="usb">
+ <displayName>USB</displayName>
+ <icon>..\connections\usb.png</icon>
+ </connection>
+ <connection name="wifi">
+ <displayName>Wi-Fi</displayName>
+ <icon>..\connections\wifi.png</icon>
+ </connection>
+ <connection name="bluetooth">
+ <displayName>Bluetooth</displayName>
+ <icon>..\connections\bluetooth.png</icon>
+ </connection>
+ </connections>
+ <types>
+ <type name="vegetables">
+ <displayName>Vegetables</displayName>
+ <icon>vegetables.png</icon>
+ </type>
+ <type name="fruits">
+ <displayName>Fruits</displayName>
+ <icon>fruits.png</icon>
+ </type>
+ <type name="grains">
+ <displayName>Grains</displayName>
+ <icon>grains.png</icon>
+ </type>
+ </types>
+ <commands>
+ <command name="sync">
+ <displayName>&amp;Sync</displayName>
+ <description>Sync things</description>
+ <icon>..\commands\sync.png</icon>
+ </command>
+ <command name="settings">
+ <displayName>Se&amp;ttings</displayName>
+ <description>Open settings page</description>
+ <icon>..\commands\settings.png</icon>
+ </command>
+ <command name="detach">
+ <displayName>&amp;Detach</displayName>
+ <description>Detach device</description>
+ <icon>..\commands\detach.png</icon>
+ </command>
+ <command name="eject">
+ <displayName>&amp;Eject</displayName>
+ <description>Eject device</description>
+ <icon>..\commands\eject.png</icon>
+ </command>
+ </commands>
+ <devices>
+ <device name="iconTest" type="test">
+ <connection></connection>
+ <displayName>Icon Test</displayName>
+ <icon width="24" height="32">..\zoom\24x32.png</icon>
+ <icon width="48" height="64">..\zoom\48x64.png</icon>
+ <icon width="96" height="128">..\zoom\96x128.png</icon>
+ <icon width="192" height="256">..\zoom\192x256.png</icon>
+ <command primary="1" disabled="0">sync</command>
+ <command primary="0" disabled="0" hidden="0" group="1">settings</command>
+ <command primary="0">detach</command>
+ <command disabled="1" hidden="0">eject</command>
+ <totalSpace>909242</totalSpace>
+ <usedSpace>0</usedSpace>
+ <hidden>0</hidden>
+ <model>Icon Test v1.0</model>
+ <status>Icon Test status string</status>
+ </device>
+ <device name="apple" type="fruits">
+ <connection>wifi</connection>
+ <displayName>Dilicious&#10;Apple</displayName>
+ <icon>apple.png</icon>
+ <totalSpace>100</totalSpace>
+ <usedSpace>0</usedSpace>
+ <hidden>0</hidden>
+ <model>iApple 2</model>
+ <command primary="1">sync</command>
+ <command group="1">eject</command>
+ </device>
+ <device name="banana" type="fruits">
+ <connection>usb</connection>
+ <displayName>Banana</displayName>
+ <icon>banana.png</icon>
+ <totalSpace>100</totalSpace>
+ <usedSpace>0</usedSpace>
+ <hidden>0</hidden>
+ <model>iBanana 2</model>
+ <command primary="1">sync</command>
+ <command group="1">eject</command>
+ </device>
+ <device name="peach" type="fruits">
+ <connection>bluetooth</connection>
+ <displayName>Peach</displayName>
+ <icon>peach.png</icon>
+ <totalSpace>100</totalSpace>
+ <usedSpace>0</usedSpace>
+ <hidden>0</hidden>
+ <model>iPeach 2</model>
+ <command primary="1">sync</command>
+ <command group="1">eject</command>
+ </device>
+ <device name="orange" type="fruits">
+ <connection>usb</connection>
+ <displayName>Orange</displayName>
+ <icon>orange.png</icon>
+ <totalSpace>100</totalSpace>
+ <usedSpace>0</usedSpace>
+ <hidden>0</hidden>
+ <model>iOrange 2</model>
+ <command primary="1">sync</command>
+ <command group="1">eject</command>
+ </device>
+ <device name="pear" type="fruits">
+ <connection>wifi</connection>
+ <displayName>Pear</displayName>
+ <icon>pear.png</icon>
+ <totalSpace>100</totalSpace>
+ <usedSpace>0</usedSpace>
+ <hidden>0</hidden>
+ <model>iPear 2</model>
+ <command primary="1">sync</command>
+ <command group="1">eject</command>
+ </device>
+ <device name="grape" type="fruits">
+ <connection>bluetooth</connection>
+ <displayName>Grape</displayName>
+ <icon>grape.png</icon>
+ <totalSpace>100</totalSpace>
+ <usedSpace>0</usedSpace>
+ <hidden>0</hidden>
+ <model>iGrape 2</model>
+ <command primary="1">sync</command>
+ <command group="1">eject</command>
+ </device>
+ <device name="lemon" type="fruits">
+ <connection>usb</connection>
+ <displayName>Lemon</displayName>
+ <icon>lemon.png</icon>
+ <totalSpace>100</totalSpace>
+ <usedSpace>0</usedSpace>
+ <hidden>0</hidden>
+ <model>iLemon 2</model>
+ <command primary="1">sync</command>
+ <command group="1">eject</command>
+ </device>
+ <device name="carrot" type="vegetables">
+ <connection>wifi</connection>
+ <displayName>Carrot</displayName>
+ <icon>carrot.png</icon>
+ <totalSpace>100</totalSpace>
+ <usedSpace>0</usedSpace>
+ <hidden>0</hidden>
+ <command primary="1">sync</command>
+ </device>
+ <device name="broccoli" type="vegetables">
+ <connection>bluetooth</connection>
+ <displayName>Broccoli</displayName>
+ <icon>broccoli.png</icon>
+ <totalSpace>100</totalSpace>
+ <usedSpace>0</usedSpace>
+ <hidden>0</hidden>
+ <command primary="1">sync</command>
+ </device>
+ <device name="eggplant" type="vegetables">
+ <connection>usb</connection>
+ <displayName>Eggplant</displayName>
+ <icon>eggplant.png</icon>
+ <totalSpace>100</totalSpace>
+ <usedSpace>0</usedSpace>
+ <hidden>0</hidden>
+ <command primary="1">sync</command>
+ </device>
+ <device name="cucumber" type="vegetables">
+ <connection>wifi</connection>
+ <displayName>Cucumber</displayName>
+ <icon>cucumber.png</icon>
+ <totalSpace>0</totalSpace>
+ <usedSpace>0</usedSpace>
+ <hidden>0</hidden>
+ <command primary="1">sync</command>
+ </device>
+ <device name="pepper" type="vegetables">
+ <connection>bluetooth</connection>
+ <displayName>Pepper</displayName>
+ <icon>pepper.png</icon>
+ <totalSpace>0</totalSpace>
+ <usedSpace>0</usedSpace>
+ <hidden>0</hidden>
+ <command primary="1">sync</command>
+ </device>
+ <device name="bread" type="grains">
+ <connection>usb</connection>
+ <displayName>Bread</displayName>
+ <icon>bread.png</icon>
+ <totalSpace>100</totalSpace>
+ <usedSpace>0</usedSpace>
+ <hidden>0</hidden>
+ </device>
+ <device name="pasta" type="grains">
+ <connection>wifi</connection>
+ <displayName>Pasta</displayName>
+ <icon>pasta.png</icon>
+ <totalSpace>100</totalSpace>
+ <usedSpace>0</usedSpace>
+ <hidden>0</hidden>
+ </device>
+ <device name="rice" type="grains">
+ <connection>bluetooth</connection>
+ <displayName>Rice</displayName>
+ <icon>rice.png</icon>
+ <totalSpace>100</totalSpace>
+ <usedSpace>0</usedSpace>
+ <hidden>0</hidden>
+ </device>
+ <device name="cereal" type="grains">
+ <connection>usb</connection>
+ <displayName>Cereal</displayName>
+ <icon>cereal.png</icon>
+ <totalSpace>100</totalSpace>
+ <usedSpace>0</usedSpace>
+ <hidden>0</hidden>
+ </device>
+ <device name="muffin" type="grains">
+ <connection>wifi</connection>
+ <displayName>Muffin</displayName>
+ <icon>muffin.png</icon>
+ <totalSpace>100</totalSpace>
+ <usedSpace>0</usedSpace>
+ <hidden>0</hidden>
+ </device>
+ <device name="pretzel" type="grains">
+ <connection>bluetooth</connection>
+ <displayName>Pretzel</displayName>
+ <icon>pretzel.png</icon>
+ <totalSpace>100</totalSpace>
+ <usedSpace>0</usedSpace>
+ <hidden>0</hidden>
+ </device>
+ </devices>
+</testprovider> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/wasabi.cpp b/Src/Plugins/Library/ml_devices/gen_deviceprovider/wasabi.cpp
new file mode 100644
index 00000000..c3079681
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/wasabi.cpp
@@ -0,0 +1,139 @@
+#include "main.h"
+#include "./wasabi.h"
+
+#include <api/service/waservicefactory.h>
+
+api_service *WASABI_API_SVC = NULL;
+api_application *WASABI_API_APP = NULL;
+api_language *WASABI_API_LNG = NULL;
+api_devicemanager *WASABI_API_DEVICES = NULL;
+
+HINSTANCE WASABI_API_LNG_HINST = NULL;
+HINSTANCE WASABI_API_ORIG_HINST = NULL;
+
+static unsigned long wasabiReference = 0;
+static BOOL defaultServicesLoaded = FALSE;
+
+static void
+Wasabi_Uninitialize()
+{
+ if (NULL != WASABI_API_SVC)
+ {
+ Wasabi_ReleaseInterface(applicationApiServiceGuid, WASABI_API_APP);
+ Wasabi_ReleaseInterface(languageApiGUID, WASABI_API_LNG);
+ Wasabi_ReleaseInterface(DeviceManagerGUID, WASABI_API_DEVICES);
+ }
+
+ WASABI_API_SVC = NULL;
+ WASABI_API_APP = NULL;
+ WASABI_API_LNG = NULL;
+ WASABI_API_DEVICES = NULL;
+ defaultServicesLoaded = FALSE;
+}
+
+BOOL
+Wasabi_Initialize(HINSTANCE instance, api_service *serviceMngr)
+{
+ if (NULL != WASABI_API_SVC)
+ return FALSE;
+
+ defaultServicesLoaded = FALSE;
+
+ WASABI_API_SVC = serviceMngr;
+ if ((api_service*)1 == WASABI_API_SVC)
+ WASABI_API_SVC = NULL;
+
+ if (NULL == WASABI_API_SVC)
+ return FALSE;
+
+ WASABI_API_APP = NULL;
+ WASABI_API_DEVICES = NULL;
+
+ WASABI_API_LNG = NULL;
+ WASABI_API_ORIG_HINST = instance;
+ WASABI_API_LNG_HINST = WASABI_API_ORIG_HINST;
+
+ Wasabi_AddRef();
+ return TRUE;
+}
+
+BOOL
+Wasabi_InitializeFromWinamp(HINSTANCE instance, HWND winampWindow)
+{
+ api_service *serviceMngr;
+ serviceMngr = (api_service*)SENDWAIPC(winampWindow, IPC_GET_API_SERVICE, 0);
+ return Wasabi_Initialize(instance, serviceMngr);
+}
+
+BOOL
+Wasabi_LoadDefaultServices(void)
+{
+ if (NULL == WASABI_API_SVC)
+ return FALSE;
+
+ if (FALSE != defaultServicesLoaded)
+ return FALSE;
+
+ WASABI_API_APP = Wasabi_QueryInterface(api_application, applicationApiServiceGuid);
+ WASABI_API_DEVICES = Wasabi_QueryInterface(api_devicemanager, DeviceManagerGUID);
+
+ WASABI_API_LNG = Wasabi_QueryInterface(api_language, languageApiGUID);
+ if (NULL != WASABI_API_LNG)
+ {
+ WASABI_API_LNG_HINST = WASABI_API_LNG->StartLanguageSupport(WASABI_API_ORIG_HINST,
+ PLUGIN_LANGUAGE_ID);
+ }
+
+ defaultServicesLoaded = TRUE;
+ return TRUE;
+}
+
+unsigned long
+Wasabi_AddRef(void)
+{
+ return InterlockedIncrement((LONG*)&wasabiReference);
+}
+
+unsigned long
+Wasabi_Release(void)
+{
+ if (0 == wasabiReference)
+ return wasabiReference;
+
+ LONG r = InterlockedDecrement((LONG*)&wasabiReference);
+ if (0 == r)
+ {
+ Wasabi_Uninitialize();
+ }
+ return r;
+}
+
+void *
+Wasabi_QueryInterface0(const GUID &interfaceGuid)
+{
+ waServiceFactory *serviceFactory;
+
+ if (NULL == WASABI_API_SVC)
+ return NULL;
+
+ serviceFactory = WASABI_API_SVC->service_getServiceByGuid(interfaceGuid);
+ if (NULL == serviceFactory)
+ return NULL;
+
+ return serviceFactory->getInterface();
+}
+
+void
+Wasabi_ReleaseInterface0(const GUID &interfaceGuid, void *interfaceInstance)
+{
+ waServiceFactory *serviceFactory;
+
+ if (NULL == WASABI_API_SVC)
+ return;
+
+ serviceFactory = WASABI_API_SVC->service_getServiceByGuid(interfaceGuid);
+ if (NULL == serviceFactory)
+ return;
+
+ serviceFactory->releaseInterface(interfaceInstance);
+}
diff --git a/Src/Plugins/Library/ml_devices/gen_deviceprovider/wasabi.h b/Src/Plugins/Library/ml_devices/gen_deviceprovider/wasabi.h
new file mode 100644
index 00000000..02ff1d40
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/gen_deviceprovider/wasabi.h
@@ -0,0 +1,57 @@
+#ifndef _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_WASABI_HEADER
+#define _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_WASABI_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <api/service/api_service.h>
+extern api_service *wasabiManager;
+#define WASABI_API_SVC wasabiManager
+
+#include <api/application/api_application.h>
+#define WASABI_API_APP applicationApi
+
+#include "../Agave/Language/api_language.h"
+
+#include "../../devices/api_devicemanager.h"
+extern api_devicemanager *deviceManagerApi;
+#define WASABI_API_DEVICES deviceManagerApi
+
+BOOL
+Wasabi_Initialize(HINSTANCE instance,
+ api_service *serviceMngr);
+
+BOOL
+Wasabi_InitializeFromWinamp(HINSTANCE instance,
+ HWND winampWindow);
+
+BOOL
+Wasabi_LoadDefaultServices(void);
+
+unsigned long
+Wasabi_AddRef(void);
+
+unsigned long
+Wasabi_Release(void);
+
+void *
+Wasabi_QueryInterface0(const GUID &interfaceGuid);
+
+#define Wasabi_QueryInterface(_interfaceType, _interfaceGuid)\
+ ((##_interfaceType*)Wasabi_QueryInterface0(_interfaceGuid))
+
+
+void
+Wasabi_ReleaseInterface0(const GUID &interfaceGuid,
+ void *interfaceInstance);
+
+#define Wasabi_ReleaseInterface(_interfaceGuid, _interfaceInstance)\
+ (Wasabi_ReleaseInterface0((_interfaceGuid), (_interfaceInstance)))
+
+
+#define Wasabi_ReleaseObject(_object)\
+ {if (NULL != (_object)) {((Dispatchable*)(_object))->Release();}}
+
+
+#endif // _NULLSOFT_WINAMP_GEN_DEVICE_PROVIDER_WASABI_HEADER
diff --git a/Src/Plugins/Library/ml_devices/graphics.cpp b/Src/Plugins/Library/ml_devices/graphics.cpp
new file mode 100644
index 00000000..7feb214b
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/graphics.cpp
@@ -0,0 +1,313 @@
+#include "main.h"
+#include "./graphics.h"
+
+BYTE
+Graphics_GetSysFontQuality()
+{
+ BOOL smoothingEnabled;
+ if (FALSE == SystemParametersInfoW(SPI_GETFONTSMOOTHING, 0, &smoothingEnabled, 0) ||
+ FALSE == smoothingEnabled)
+ {
+ return DEFAULT_QUALITY;
+ }
+
+ OSVERSIONINFOW vi;
+ vi.dwOSVersionInfoSize = sizeof(vi);
+ if (FALSE == GetVersionExW(&vi))
+ return DEFAULT_QUALITY;
+
+ if (vi.dwMajorVersion > 5 || (vi.dwMajorVersion == 5 && vi.dwMinorVersion >= 1))
+ {
+ UINT smootingType;
+ if (FALSE == SystemParametersInfoW(SPI_GETFONTSMOOTHINGTYPE, 0, &smootingType, 0))
+ return DEFAULT_QUALITY;
+
+ if (FE_FONTSMOOTHINGCLEARTYPE == smootingType)
+ return CLEARTYPE_NATURAL_QUALITY/*CLEARTYPE_QUALITY*/;
+ }
+
+ return ANTIALIASED_QUALITY;
+}
+
+HFONT
+Graphics_CreateSysFont()
+{
+ LOGFONTW lf = {0};
+ HFONT font = NULL;
+
+ if (FALSE == SystemParametersInfoW(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0))
+ return NULL;
+
+ lf.lfQuality = Graphics_GetSysFontQuality();
+ font = CreateFontIndirectW(&lf);
+
+ return font;
+}
+
+HFONT
+Graphics_DuplicateFont(HFONT sourceFont, INT heightDeltaPt, BOOL forceBold, BOOL systemQuality)
+{
+ LOGFONTW lf = {0};
+
+ if (NULL == sourceFont)
+ return NULL;
+
+
+ if (sizeof(lf) != GetObjectW(sourceFont, sizeof(lf), &lf))
+ return NULL;
+
+ if (0 != heightDeltaPt)
+ {
+ HDC hdcTmp = NULL, hdc = GetDCEx(NULL, NULL, DCX_WINDOW | DCX_CACHE | DCX_NORESETATTRS);
+
+ if (NULL != hdc)
+ {
+ hdcTmp = CreateCompatibleDC(hdc);
+ ReleaseDC(NULL, hdc);
+ }
+
+ if (NULL == hdcTmp)
+ return NULL;
+
+ LONG pixelsY = GetDeviceCaps(hdcTmp, LOGPIXELSY);
+ HFONT prevFont = SelectFont(hdcTmp, sourceFont);
+
+ TEXTMETRICW tm = {0};
+ if (FALSE != GetTextMetricsW(hdcTmp, &tm))
+ {
+ INT basePt = MulDiv(tm.tmHeight - tm.tmInternalLeading, 96, pixelsY);
+ lf.lfHeight = -MulDiv((basePt + heightDeltaPt), pixelsY, 96);
+
+ }
+
+ SelectObject(hdcTmp, prevFont);
+ DeleteDC(hdcTmp);
+ }
+
+ if (FALSE != systemQuality)
+ lf.lfQuality = Graphics_GetSysFontQuality();
+ if (FALSE != forceBold && lf.lfWeight < FW_BOLD)
+ lf.lfWeight = FW_BOLD;
+
+ return CreateFontIndirectW(&lf);
+}
+
+long
+Graphics_GetFontHeight(HDC hdc)
+{
+ TEXTMETRICW tm;
+ if (FALSE == GetTextMetricsW(hdc, &tm))
+ return 0;
+
+ return tm.tmHeight;
+}
+
+long
+Graphics_GetAveStrWidth(HDC hdc, UINT cchLen)
+{
+ const char szTest[] =
+ {
+ 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', 'Q','R','S','T','U','V','W','X','Y','Z',
+ 'a','b','c','d','e','f','g','h','i','j','k','l', 'm','n','o','p','q','r','s','t','u','v','w','x','y','z'
+ };
+
+ SIZE textSize;
+ if (FALSE == GetTextExtentPointA(hdc, szTest, ARRAYSIZE(szTest) -1, &textSize))
+ return 0;
+
+ LONG result;
+ if (1 == cchLen)
+ {
+ result = (textSize.cx + ARRAYSIZE(szTest)/2)/ARRAYSIZE(szTest);
+ }
+ else
+ {
+ result = MulDiv(cchLen, textSize.cx + ARRAYSIZE(szTest)/2, ARRAYSIZE(szTest));
+ if (0 != result)
+ {
+ TEXTMETRICW tm;
+ if (FALSE != GetTextMetricsW(hdc, &tm))
+ result += tm.tmOverhang;
+ }
+ }
+ return result;
+}
+
+BOOL
+Graphics_GetWindowBaseUnits(HWND hwnd, LONG *baseUnitX, LONG *baseUnitY)
+{
+ BOOL result;
+ result = FALSE;
+
+ if (NULL != hwnd)
+ {
+ HDC hdc = GetDCEx(hwnd, NULL, DCX_CACHE | DCX_NORESETATTRS);
+ if (NULL != hdc)
+ {
+ TEXTMETRICW tm;
+ HFONT font, prevFont;
+
+ font = (HFONT)SNDMSG(hwnd, WM_GETFONT, 0, 0L);
+ prevFont = SelectFont(hdc, font);
+
+ if (FALSE != GetTextMetricsW(hdc, &tm))
+ {
+ if (NULL != baseUnitX)
+ *baseUnitX = Graphics_GetAveStrWidth(hdc, 1);
+
+ if (NULL != baseUnitY)
+ *baseUnitY = tm.tmHeight;
+
+ result = TRUE;
+ }
+
+ SelectFont(hdc, prevFont);
+ ReleaseDC(hwnd, hdc);
+ }
+ }
+ return result;
+}
+
+
+typedef int (*SkinColorFunc)(int idx);
+
+COLORREF Graphics_GetSkinColor(unsigned int colorIndex)
+{
+ static SkinColorFunc GetSkinColor = NULL;
+
+ if (NULL == GetSkinColor)
+ {
+ GetSkinColor = (SkinColorFunc)SENDMLIPC(Plugin_GetLibraryWindow(), ML_IPC_SKIN_WADLG_GETFUNC, 1);
+ if (NULL == GetSkinColor)
+ return RGB(255, 0, 255);
+ }
+
+ return GetSkinColor(colorIndex);
+}
+
+COLORREF
+Graphics_BlendColors(COLORREF rgbTop, COLORREF rgbBottom, INT alpha)
+{
+ if (alpha > 254) return rgbTop;
+ if (alpha < 0) return rgbBottom;
+
+ WORD k = (WORD)(((255 - alpha)*255 + 127)/255);
+
+ return RGB( (GetRValue(rgbTop)*alpha + k*GetRValue(rgbBottom) + 127)/255,
+ (GetGValue(rgbTop)*alpha + k*GetGValue(rgbBottom) + 127)/255,
+ (GetBValue(rgbTop)*alpha + k*GetBValue(rgbBottom) + 127)/255);
+}
+
+INT
+Graphics_GetColorDistance(COLORREF rgb1, COLORREF rgb2)
+{
+ return (1000 * ((GetRValue(rgb1) - GetRValue(rgb2)) +
+ (GetGValue(rgb1) - GetGValue(rgb2)) +
+ (GetBValue(rgb1) - GetBValue(rgb2))))/ (3 * 255);
+}
+
+void
+Graphics_ClampRect(RECT *rect, const RECT *boxRect)
+{
+ if (rect->left < boxRect->left)
+ rect->left = boxRect->left;
+
+ if (rect->top < boxRect->top)
+ rect->top = boxRect->top;
+
+ if (rect->right > boxRect->right)
+ rect->right = boxRect->right;
+
+ if (rect->bottom > boxRect->bottom)
+ rect->bottom = boxRect->bottom;
+}
+
+void
+Graphics_NormalizeRect(RECT *rect)
+{
+ if (rect->top > rect->bottom)
+ rect->bottom = rect->top;
+
+ if (rect->left > rect->right)
+ rect->right = rect->left;
+}
+
+void
+Graphics_GetRectSizeNormalized(const RECT *rect, SIZE *size)
+{
+ size->cx = rect->right - rect->left;
+ if (size->cx < 0)
+ size->cx = 0;
+
+ size->cy = rect->bottom - rect->top;
+ if (size->cy < 0)
+ size->cy = 0;
+}
+
+BOOL
+Graphics_IsRectFit(const RECT *rect, const RECT *boxRect)
+{
+ if (rect->left < boxRect->left ||
+ rect->top < boxRect->top ||
+ rect->right > boxRect->right ||
+ rect->bottom > boxRect->bottom)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+BOOL SetSizeEmpty(SIZE *size)
+{
+ if (NULL == size)
+ return FALSE;
+
+ ZeroMemory(size, sizeof(SIZE));
+ return TRUE;
+}
+
+BOOL IsSizeEmpty(SIZE *size)
+{
+ return (NULL == size || 0 == size->cx || 0 == size->cy);
+}
+
+BOOL SetSize(SIZE *size, long width, long height)
+{
+ if (NULL == size)
+ return FALSE;
+
+ size->cx = width;
+ size->cy = height;
+
+ return TRUE;
+}
+
+BOOL SetPoint(POINT *pt, long x, long y)
+{
+ if (NULL == pt)
+ return FALSE;
+
+ pt->x = x;
+ pt->y = y;
+
+ return TRUE;
+}
+
+BOOL MakeRectPolygon(POINT vertices[4], long left, long top, long right, long bottom)
+{
+ if (NULL == vertices)
+ return FALSE;
+
+ vertices[0].x = left;
+ vertices[0].y = top;
+ vertices[1].x = right;
+ vertices[1].y = top;
+ vertices[2].x = right;
+ vertices[2].y = bottom;
+ vertices[3].x = left;
+ vertices[3].y = bottom;
+
+ return TRUE;
+}
diff --git a/Src/Plugins/Library/ml_devices/graphics.h b/Src/Plugins/Library/ml_devices/graphics.h
new file mode 100644
index 00000000..7aab1dce
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/graphics.h
@@ -0,0 +1,72 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_GRAPHICS_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_GRAPHICS_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#ifndef SelectBitmap
+ #define SelectBitmap(_hdc, _bitmap) ((HBITMAP)SelectObject(_hdc, _bitmap))
+#endif
+
+#ifndef SelectFont
+ #define SelectFont(_hdc, _font) ((HFONT)SelectObject(_hdc, _font))
+#endif
+
+#ifndef SelectBrush
+ #define SelectBrush(_hdc, _brush) ((HBRUSH)SelectObject(_hdc, _brush))
+#endif
+
+#ifndef GetCurrentBitmap
+ #define GetCurrentBitmap(_hdc) ((HBITMAP)GetCurrentObject(_hdc, OBJ_BITMAP))
+#endif
+
+#ifndef GetCurrentFont
+ #define GetCurrentFont(_hdc) ((HFONT)GetCurrentObject(_hdc, OBJ_FONT))
+#endif
+
+#ifndef GetCurrentBrush
+ #define GetCurrentBrush(_hdc) ((HBRUSH)GetCurrentObject(_hdc, OBJ_BRUSH))
+#endif
+
+
+
+#ifndef SET_GRADIENT_VERTEX
+ #define SET_GRADIENT_VERTEX(_vertex, _x, _y, _rgb)\
+ {(_vertex).x = (_x); (_vertex).y = (_y);\
+ (_vertex).Red = GetRValue(_rgb) << 8; (_vertex).Green = GetGValue(_rgb) << 8;\
+ (_vertex).Blue = GetBValue(_rgb) << 8; (_vertex).Alpha = 0x0000;}
+#endif
+
+#ifndef SET_GRADIENT_RECT_MESH
+ #define SET_GRADIENT_RECT_MESH(_mesh, _upperLeft, _bottomRight)\
+ {(_mesh).UpperLeft = (_upperLeft); (_mesh).LowerRight = (_bottomRight);}
+#endif
+
+
+BYTE Graphics_GetSysFontQuality();
+HFONT Graphics_CreateSysFont();
+HFONT Graphics_DuplicateFont(HFONT sourceFont, INT heightDeltaPt, BOOL forceBold, BOOL systemQuality);
+
+long Graphics_GetFontHeight(HDC hdc);
+long Graphics_GetAveStrWidth(HDC hdc, UINT cchLen);
+BOOL Graphics_GetWindowBaseUnits(HWND hwnd, LONG *baseUnitX, LONG *baseUnitY);
+
+COLORREF Graphics_GetSkinColor(unsigned int colorIndex);
+COLORREF Graphics_BlendColors(COLORREF rgbTop, COLORREF rgbBottom, INT alpha);
+INT Graphics_GetColorDistance(COLORREF rgb1, COLORREF rgb2);
+
+void Graphics_ClampRect(RECT *rect, const RECT *boxRect);
+void Graphics_NormalizeRect(RECT *rect);
+void Graphics_GetRectSizeNormalized(const RECT *rect, SIZE *size);
+BOOL Graphics_IsRectFit(const RECT *rect, const RECT *boxRect);
+
+BOOL SetSizeEmpty(SIZE *size);
+BOOL IsSizeEmpty(SIZE *size);
+BOOL SetSize(SIZE *size, long width, long height);
+
+BOOL SetPoint(POINT *pt, long x, long y);
+BOOL MakeRectPolygon(POINT vertices[4], long left, long top, long right, long bottom);
+
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_GRAPHICS_HEADER
diff --git a/Src/Plugins/Library/ml_devices/image.cpp b/Src/Plugins/Library/ml_devices/image.cpp
new file mode 100644
index 00000000..5bcb0626
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/image.cpp
@@ -0,0 +1,978 @@
+#include "main.h"
+#include "./image.h"
+
+
+HBITMAP
+Image_Load(const wchar_t *path, unsigned int type,
+ unsigned int flags, int width, int height)
+{
+ MLIMAGESOURCE source;
+ HWND libraryWindow = Plugin_GetLibraryWindow();
+ if (NULL == libraryWindow)
+ return NULL;
+
+ source.cbSize = sizeof(source);
+ source.lpszName = path;
+ source.type = type;
+ source.flags = (flags & ~IMAGE_FILTER_MASK);
+ source.cxDst = width;
+ source.cyDst = height;
+
+ if (0 == (ISF_LOADFROMFILE & source.flags))
+ {
+ source.hInst = WASABI_API_LNG_HINST;
+ if (NULL != source.hInst)
+ {
+ HBITMAP bitmap = MLImageLoader_LoadDib(libraryWindow, &source);
+ if (NULL != bitmap)
+ return bitmap;
+ }
+
+ if (WASABI_API_ORIG_HINST == source.hInst)
+ return NULL;
+
+ source.hInst = WASABI_API_ORIG_HINST;
+ return (NULL != source.hInst) ?
+ MLImageLoader_LoadDib(libraryWindow, &source) :
+ NULL;
+ }
+
+ return MLImageLoader_LoadDib(Plugin_GetLibraryWindow(), &source);
+}
+
+HBITMAP
+Image_LoadEx(HINSTANCE instance, const wchar_t *path, unsigned int type,
+ unsigned int flags, int width, int height)
+{
+ MLIMAGESOURCE source = {0};
+
+ source.cbSize = sizeof(source);
+ source.hInst = instance;
+ source.lpszName = path;
+ source.type = type;
+ source.flags = (flags & ~IMAGE_FILTER_MASK);
+ source.cxDst = width;
+ source.cyDst = height;
+
+ return MLImageLoader_LoadDib(Plugin_GetLibraryWindow(), &source);
+}
+
+BOOL
+Image_FilterEx(void *pixelData, long width, long height, unsigned short bpp,
+ unsigned int flags, COLORREF backColor, COLORREF frontColor, COLORREF blendColor)
+{
+ MLIMAGEFILTERAPPLYEX filter;
+ HWND libraryWindow;
+ BOOL result;
+
+ if (NULL == pixelData)
+ return FALSE;
+
+ libraryWindow = Plugin_GetLibraryWindow();
+ if (NULL == libraryWindow)
+ return FALSE;
+
+ filter.cbSize = sizeof(filter);
+ filter.pData = (BYTE*)pixelData;
+ filter.cx = width;
+ filter.cy = height;
+ filter.bpp = bpp;
+ filter.imageTag = NULL;
+
+ result = FALSE;
+
+ if (0 != (IMAGE_FILTER_GRAYSCALE & flags))
+ {
+ filter.filterUID = MLIF_GRAYSCALE_UID;
+ result = MLImageFilter_ApplyEx(libraryWindow, &filter);
+ }
+
+ filter.rgbBk = backColor;
+ filter.rgbFg = frontColor;
+
+ if (32 == bpp)
+ {
+ filter.filterUID = MLIF_FILTER1_PRESERVE_ALPHA_UID;
+ result = MLImageFilter_ApplyEx(libraryWindow, &filter);
+
+ if (0 != (IMAGE_FILTER_BLEND & flags))
+ {
+ filter.filterUID = MLIF_BLENDONBK_UID;
+ filter.rgbBk = blendColor;
+ result = MLImageFilter_ApplyEx(libraryWindow, &filter);
+ }
+ }
+ else
+ {
+ filter.filterUID = MLIF_FILTER1_UID;
+ result = MLImageFilter_ApplyEx(libraryWindow, &filter);
+ }
+
+ return result;
+}
+
+BOOL
+Image_Filter(HBITMAP bitmap, unsigned int flags,
+ COLORREF backColor, COLORREF frontColor, COLORREF blendColor)
+{
+ DIBSECTION bitmapData;
+ BITMAP *bi;
+
+ if (NULL == bitmap)
+ return NULL;
+
+ if (sizeof(bitmapData) != GetObjectW(bitmap, sizeof(bitmapData), &bitmapData))
+ return FALSE;
+
+ bi = &bitmapData.dsBm;
+
+ return Image_FilterEx(bi->bmBits, bi->bmWidth, bi->bmHeight, bi->bmBitsPixel,
+ flags, backColor, frontColor, blendColor);
+}
+
+BOOL
+Image_BlendEx(void *pixelData, long width, long height, unsigned short bpp, COLORREF blendColor)
+{
+ MLIMAGEFILTERAPPLYEX filter;
+ HWND libraryWindow;
+
+ if (NULL == pixelData || 32 != bpp)
+ return FALSE;
+
+ libraryWindow = Plugin_GetLibraryWindow();
+ if (NULL == libraryWindow)
+ return FALSE;
+
+ filter.cbSize = sizeof(filter);
+ filter.pData = (BYTE*)pixelData;
+ filter.cx = width;
+ filter.cy = height;
+ filter.bpp = bpp;
+ filter.imageTag = NULL;
+
+
+ filter.filterUID = MLIF_BLENDONBK_UID;
+ filter.rgbBk = blendColor;
+ return MLImageFilter_ApplyEx(libraryWindow, &filter);
+}
+
+BOOL
+Image_Blend(HBITMAP bitmap, COLORREF blendColor)
+{
+ DIBSECTION bitmapData;
+ BITMAP *bi;
+
+ if (NULL == bitmap)
+ return NULL;
+
+ if (sizeof(bitmapData) != GetObjectW(bitmap, sizeof(bitmapData), &bitmapData))
+ return FALSE;
+
+ bi = &bitmapData.dsBm;
+
+ return Image_BlendEx(bi->bmBits, bi->bmWidth, bi->bmHeight, bi->bmBitsPixel, blendColor);
+}
+
+HBITMAP
+Image_LoadSkinnedEx(HINSTANCE instance, const wchar_t *path, unsigned int type,
+ unsigned int flags, int width, int height,
+ COLORREF backColor, COLORREF frontColor, COLORREF blendColor)
+{
+ HBITMAP bitmap;
+
+ bitmap = Image_LoadEx(instance, path, type, flags, width, height);
+ if (NULL == bitmap)
+ return NULL;
+
+ Image_Filter(bitmap, flags, backColor, frontColor, blendColor);
+
+ return bitmap;
+}
+
+HBITMAP
+Image_LoadSkinned(const wchar_t *path, unsigned int type,
+ unsigned int flags, int width, int height,
+ COLORREF backColor, COLORREF frontColor, COLORREF blendColor)
+{
+ HBITMAP bitmap;
+
+ bitmap = Image_Load(path, type, flags, width, height);
+ if (NULL == bitmap)
+ return NULL;
+
+ Image_Filter(bitmap, flags, backColor, frontColor, blendColor);
+
+ return bitmap;
+}
+
+HBITMAP
+Image_DuplicateDib(HBITMAP source)
+{
+ HBITMAP bitmap;
+ DIBSECTION sourceDib;
+ HDC windowDC;
+ void *pixelData;
+
+ if (NULL == source)
+ return NULL;
+
+ if (sizeof(sourceDib) != GetObjectW(source, sizeof(sourceDib), &sourceDib))
+ return FALSE;
+
+
+ sourceDib.dsBmih.biSize = sizeof(BITMAPINFOHEADER);
+ if (sourceDib.dsBmih.biHeight > 0)
+ sourceDib.dsBmih.biHeight = -sourceDib.dsBmih.biHeight;
+
+ windowDC = GetDCEx(NULL, NULL, DCX_WINDOW | DCX_CACHE);
+
+ bitmap = CreateDIBSection(windowDC, (BITMAPINFO*)&sourceDib.dsBmih, DIB_RGB_COLORS, &pixelData, NULL, 0);
+
+ if (NULL != windowDC)
+ ReleaseDC(NULL, windowDC);
+
+ if (NULL == bitmap)
+ return NULL;
+
+ CopyMemory(pixelData, sourceDib.dsBm.bmBits, sourceDib.dsBm.bmWidthBytes * sourceDib.dsBm.bmHeight);
+
+ return bitmap;
+
+}
+BOOL
+Image_ColorOver(HBITMAP bitmap, const RECT *prcPart, BOOL premult, COLORREF rgb)
+{
+ DIBSECTION bitmapData;
+ BITMAP *bi;
+
+ if (NULL == bitmap)
+ return NULL;
+
+ if (sizeof(bitmapData) != GetObjectW(bitmap, sizeof(bitmapData), &bitmapData))
+ return FALSE;
+
+ if (BI_RGB != bitmapData.dsBmih.biCompression ||
+ 1 != bitmapData.dsBmih.biPlanes ||
+ 32 != bitmapData.dsBm.bmBitsPixel)
+ {
+ return FALSE;
+ }
+
+ bi = &bitmapData.dsBm;
+
+ return (NULL == prcPart) ?
+ Image_ColorOverEx((BYTE*)bi->bmBits, bi->bmWidth, bi->bmHeight,
+ 0, 0, bi->bmWidth, bi->bmHeight,
+ bi->bmBitsPixel, premult, rgb) :
+ Image_ColorOverEx((BYTE*)bi->bmBits, bi->bmWidth, bi->bmHeight,
+ prcPart->left, prcPart->top,
+ prcPart->right - prcPart->left, prcPart->bottom - prcPart->top,
+ bi->bmBitsPixel, premult, rgb);
+
+}
+
+BOOL
+Image_ColorOverEx(unsigned char *pPixels, int bitmapCX, int bitmapCY,
+ long x, long y, long cx, long cy, unsigned short bpp,
+ BOOL premult, COLORREF rgb)
+{
+ LONG pitch;
+ UINT a, r, g, b, ma, mr, mg, mb;
+ INT step = (bpp>>3);
+ LPBYTE line, cursor;
+ pitch = bitmapCX * step;
+ while (pitch%4) pitch++;
+
+ if (step < 3)
+ return TRUE;
+
+ if (cy < 0) cy -= cy;
+
+ a = (LOBYTE((rgb)>>24)); r = GetRValue(rgb); g = GetGValue(rgb); b = GetBValue(rgb);
+ ma = 255 - a; mr = r * 255; mg = g * 255; mb = b * 255;
+
+ if (0 == a)
+ return TRUE;
+
+ INT ofs = (bitmapCY > 0) ? (bitmapCY - (y + cy)) : y;
+ line = pPixels + pitch * ofs + x*step;
+
+ if (0xFF == a)
+ {
+ for (; cy-- != 0; line += pitch)
+ {
+ for (x = cx, cursor = line; x-- != 0; cursor += step)
+ {
+ cursor[0] = (BYTE)b;
+ cursor[1] = (BYTE)g;
+ cursor[2] = (BYTE)r;
+ // cursor[3] = 0xFF;
+ }
+ }
+ return TRUE;
+ }
+
+
+ if (premult)
+ {
+ for (; cy-- != 0; line += pitch)
+ {
+ for (x = cx, cursor = line; x-- != 0; cursor += step)
+ {
+ int t = (mb + ma * cursor[0] + 127) / 255;
+ cursor[0] = (t > 0xFF) ? 0xFF : t;
+ t = (mg + ma * cursor[1] + 127) / 255;
+ cursor[1] = (t > 0xFF) ? 0xFF : t;
+ t = (mr+ ma * cursor[2] + 127) / 255;
+ cursor[2] = (t > 0xFF) ? 0xFF : t;
+ }
+ }
+ }
+ else
+ {
+ WORD k = (((255 - a)*255 + 127)/255);
+ for (; cy-- != 0; line += pitch)
+ {
+ for (x = cx, cursor = line; x-- != 0; cursor += step)
+ {
+ cursor[0] = (b*a + k*cursor[0] + 127)/255;
+ cursor[1] = (g*a + k*cursor[1] + 127)/255;
+ cursor[2] = (r*a + k*cursor[2] + 127)/255;
+ // cursor[3] = (a*a + k*cursor[3] + 127)/255;
+ }
+ }
+
+ }
+ return TRUE;
+}
+
+BOOL
+Image_Premultiply(HBITMAP hbmp, const RECT *prcPart)
+{
+ DIBSECTION dibsec;
+ if (!hbmp || sizeof(DIBSECTION) != GetObject(hbmp, sizeof(DIBSECTION), &dibsec) ||
+ BI_RGB != dibsec.dsBmih.biCompression || 1 != dibsec.dsBmih.biPlanes || dibsec.dsBm.bmBitsPixel != 32)
+ return FALSE;
+
+ return (NULL == prcPart) ?
+ Image_PremultiplyEx((BYTE*)dibsec.dsBm.bmBits, dibsec.dsBm.bmWidth, dibsec.dsBm.bmHeight,
+ 0, 0, dibsec.dsBm.bmWidth, dibsec.dsBm.bmHeight,
+ dibsec.dsBm.bmBitsPixel) :
+ Image_PremultiplyEx((BYTE*)dibsec.dsBm.bmBits, dibsec.dsBm.bmWidth, dibsec.dsBm.bmHeight,
+ prcPart->left, prcPart->top, RECTWIDTH(*prcPart), RECTHEIGHT(*prcPart),
+ dibsec.dsBm.bmBitsPixel);
+}
+
+BOOL
+Image_PremultiplyEx(unsigned char *pPixels, int bitmapCX, int bitmapCY,
+ long x, long y, long cx, long cy, unsigned short bpp)
+{
+ if (32 != bpp)
+ return FALSE;
+
+ LONG pitch;
+ INT step = (bpp>>3);
+ LPBYTE line, cursor;
+ pitch = bitmapCX * step;
+ while (pitch%4) pitch++;
+
+ if (cy < 0)
+ cy = -cy;
+
+ INT ofs = (bitmapCY > 0) ? (bitmapCY - (y + cy)) : y;
+ line = pPixels + pitch * ofs + x*step;
+
+ UINT a;
+ for (; cy-- != 0; line += pitch)
+ {
+ for (x = cx, cursor = line; x-- != 0; cursor += step)
+ {
+ a = cursor[3];
+ if (0 == a)
+ {
+ cursor[0] = 0;
+ cursor[1] = 0;
+ cursor[2] = 0;
+ }
+ else if (255 != a)
+ {
+ cursor[0] = (BYTE)MulDiv(cursor[0], a, 255);
+ cursor[1] = (BYTE)MulDiv(cursor[1], a, 255);
+ cursor[2] = (BYTE)MulDiv(cursor[2], a, 255);
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL
+Image_Demultiply(HBITMAP hbmp, const RECT *prcPart)
+{
+ DIBSECTION dibsec;
+ if (!hbmp || sizeof(DIBSECTION) != GetObject(hbmp, sizeof(DIBSECTION), &dibsec) ||
+ BI_RGB != dibsec.dsBmih.biCompression || 1 != dibsec.dsBmih.biPlanes || dibsec.dsBm.bmBitsPixel != 32)
+ return FALSE;
+
+ return (NULL == prcPart) ?
+ Image_DemultiplyEx((BYTE*)dibsec.dsBm.bmBits, dibsec.dsBm.bmWidth, dibsec.dsBm.bmHeight,
+ 0, 0, dibsec.dsBm.bmWidth, dibsec.dsBm.bmHeight,
+ dibsec.dsBm.bmBitsPixel) :
+ Image_DemultiplyEx((BYTE*)dibsec.dsBm.bmBits, dibsec.dsBm.bmWidth, dibsec.dsBm.bmHeight,
+ prcPart->left, prcPart->top, RECTWIDTH(*prcPart), RECTHEIGHT(*prcPart),
+ dibsec.dsBm.bmBitsPixel);
+}
+
+BOOL
+Image_DemultiplyEx(unsigned char *pPixels, int bitmapCX, int bitmapCY,
+ long x, long y, long cx, long cy, unsigned short bpp)
+{
+ if (32 != bpp)
+ return FALSE;
+
+ LONG pitch;
+ INT step = (bpp>>3);
+ LPBYTE line, cursor;
+ pitch = bitmapCX * step;
+ while (pitch%4) pitch++;
+
+ if (cy < 0)
+ cy = -cy;
+
+ INT ofs = (bitmapCY > 0) ? (bitmapCY - (y + cy)) : y;
+ line = pPixels + pitch * ofs + x*step;
+
+ UINT a;
+ for (; cy-- != 0; line += pitch)
+ {
+ for (x = cx, cursor = line; x-- != 0; cursor += step)
+ {
+ a = cursor[3];
+ if (0 == a)
+ {
+ cursor[0] = 0;
+ cursor[1] = 0;
+ cursor[2] = 0;
+ }
+ else if (255 != a)
+ {
+ cursor[0] = (BYTE)MulDiv(cursor[0], 255, a);
+ cursor[1] = (BYTE)MulDiv(cursor[1], 255, a);
+ cursor[2] = (BYTE)MulDiv(cursor[2], 255, a);
+ }
+ }
+ }
+
+ return TRUE;
+}
+BOOL
+Image_Saturate(HBITMAP hbmp, const RECT *prcPart, int n, BOOL fScale)
+{
+ DIBSECTION dibsec;
+ if (!hbmp || sizeof(DIBSECTION) != GetObject(hbmp, sizeof(DIBSECTION), &dibsec) ||
+ BI_RGB != dibsec.dsBmih.biCompression || 1 != dibsec.dsBmih.biPlanes || dibsec.dsBm.bmBitsPixel != 32)
+ return FALSE;
+
+ return Image_SaturateEx((BYTE*)dibsec.dsBm.bmBits, dibsec.dsBm.bmWidth, dibsec.dsBm.bmHeight,
+ prcPart->left, prcPart->top,
+ prcPart->right - prcPart->left, prcPart->bottom - prcPart->top,
+ dibsec.dsBm.bmBitsPixel, n, fScale);
+}
+
+BOOL
+Image_SaturateEx(unsigned char *pPixels, int bitmapCX, int bitmapCY,
+ long x, long y, long cx, long cy, unsigned short bpp,
+ int n, BOOL fScale)
+{
+ if (32 != bpp)
+ return FALSE;
+
+ LONG pitch;
+ INT step = (bpp>>3);
+ LPBYTE line, cursor;
+ pitch = bitmapCX * step;
+ while (pitch%4) pitch++;
+
+ if (FALSE == fScale)
+ {
+ if (n < 0) n = 0;
+ else if (n > 1000) n = 1000;
+ }
+ else
+ {
+ if (n < -1000) n = -1000;
+ else if (n > 1000) n = 1000;
+ }
+
+ if (cy < 0)
+ cy = -cy;
+
+ INT ofs = (bitmapCY > 0) ? (bitmapCY - (y + cy)) : y;
+ line = pPixels + pitch * ofs + x*step;
+
+ COLORREF rgb;
+ INT k;
+ WORD h, l, s;
+
+ for (; cy-- != 0; line += pitch)
+ {
+ for (x = cx, cursor = line; x-- != 0; cursor += step)
+ {
+ rgb = RGB(cursor[2], cursor[1], cursor[0]);
+ ColorRGBToHLS(rgb, &h, &l, &s);
+ if(FALSE == fScale)
+ s = ((WORD)((240 * n)/1000));
+ else
+ {
+ k = s;
+ s = (WORD)(k + (k * n) /1000);
+ }
+
+ rgb = ColorHLSToRGB(h, l, s);
+
+ cursor[0] = GetBValue(rgb);
+ cursor[1] = GetGValue(rgb);
+ cursor[2] = GetRValue(rgb);
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL
+Image_AdjustAlpha(HBITMAP hbmp, const RECT *prcPart, int n, BOOL fScale)
+{
+ DIBSECTION dibsec;
+ if (!hbmp || sizeof(DIBSECTION) != GetObject(hbmp, sizeof(DIBSECTION), &dibsec) ||
+ BI_RGB != dibsec.dsBmih.biCompression || 1 != dibsec.dsBmih.biPlanes || dibsec.dsBm.bmBitsPixel != 32)
+ return FALSE;
+
+ return Image_AdjustAlphaEx((BYTE*)dibsec.dsBm.bmBits, dibsec.dsBm.bmWidth, dibsec.dsBm.bmHeight,
+ prcPart->left, prcPart->top,
+ prcPart->right - prcPart->left, prcPart->bottom - prcPart->top,
+ dibsec.dsBm.bmBitsPixel, n, fScale);
+}
+
+BOOL
+Image_AdjustAlphaEx(unsigned char *pPixels, int bitmapCX, int bitmapCY,
+ long x, long y, long cx, long cy, unsigned short bpp,
+ int n, BOOL fScale)
+{
+ if (32 != bpp)
+ return FALSE;
+
+ LONG pitch;
+ INT step = (bpp>>3);
+ LPBYTE line, cursor;
+ pitch = bitmapCX * step;
+ while (pitch%4) pitch++;
+
+ if (FALSE == fScale)
+ {
+ if (n < 0) n = 0;
+ else if (n > 1000) n = 1000;
+ }
+ else
+ {
+ if (n < -1000) n = -1000;
+ else if (n > 1000) n = 1000;
+ }
+
+ if (cy < 0)
+ cy = -cy;
+
+ INT ofs = (bitmapCY > 0) ? (bitmapCY - (y + cy)) : y;
+ line = pPixels + pitch * ofs + x*step;
+
+ INT k;
+
+ for (; cy-- != 0; line += pitch)
+ {
+ for (x = cx, cursor = line; x-- != 0; cursor += step)
+ {
+ if(FALSE == fScale)
+ cursor[3] = ((BYTE)((255 * n)/1000));
+ else
+ {
+ k = cursor[3];
+ k = k + MulDiv(k, n, 1000);
+ if (k > 255) k = 255;
+ cursor[3] = (BYTE)k;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL
+Image_AdjustSaturationAlpha(HBITMAP hbmp, const RECT *prcPart, int nSaturation, int nAlpha)
+{
+ DIBSECTION dibsec;
+ if (!hbmp || sizeof(DIBSECTION) != GetObject(hbmp, sizeof(DIBSECTION), &dibsec) ||
+ BI_RGB != dibsec.dsBmih.biCompression || 1 != dibsec.dsBmih.biPlanes || dibsec.dsBm.bmBitsPixel != 32)
+ return FALSE;
+
+ return Image_AdjustSaturationAlphaEx((BYTE*)dibsec.dsBm.bmBits, dibsec.dsBm.bmWidth, dibsec.dsBm.bmHeight,
+ prcPart->left, prcPart->top,
+ prcPart->right - prcPart->left, prcPart->bottom - prcPart->top,
+ dibsec.dsBm.bmBitsPixel, nSaturation, nAlpha);
+}
+
+BOOL
+Image_AdjustSaturationAlphaEx(unsigned char *pPixels, int bitmapCX, int bitmapCY,
+ long x, long y, long cx, long cy,
+ unsigned short bpp, int nSaturation, int nAlpha)
+{
+ if (32 != bpp)
+ return FALSE;
+
+ LONG pitch;
+ INT step = (bpp>>3);
+ LPBYTE line, cursor;
+ pitch = bitmapCX * step;
+ while (pitch%4) pitch++;
+
+ if (nSaturation < -1000) nSaturation = -1000;
+ else if (nSaturation > 1000) nSaturation = 1000;
+
+ if (nAlpha < -1000) nAlpha = -1000;
+ else if (nAlpha > 1000) nAlpha = 1000;
+
+ if (cy < 0)
+ cy = -cy;
+
+ INT ofs = (bitmapCY > 0) ? (bitmapCY - (y + cy)) : y;
+ line = pPixels + pitch * ofs + x*step;
+
+ INT k;
+ COLORREF rgb;
+ WORD h, l, s;
+
+ for (; cy-- != 0; line += pitch)
+ {
+ for (x = cx, cursor = line; x-- != 0; cursor += step)
+ {
+ k = cursor[3];
+ k = k + MulDiv(k, nAlpha, 1000);
+ if (k > 255) k = 255;
+ cursor[3] = (BYTE)k;
+
+ rgb = RGB(cursor[2], cursor[1], cursor[0]);
+ ColorRGBToHLS(rgb, &h, &l, &s);
+
+ k = s;
+ k = k + MulDiv(k, nSaturation, 1000);
+ if (k > 240) k = 240;
+ s = (WORD)k;
+
+ rgb = ColorHLSToRGB(h, l, s);
+ cursor[0] = GetBValue(rgb);
+ cursor[1] = GetGValue(rgb);
+ cursor[2] = GetRValue(rgb);
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL
+Image_FillBorder(HDC targetDC, const RECT *targetRect,
+ HDC sourceDC, const RECT *sourceRect,
+ BOOL fillCenter, BYTE alphaConstant)
+{
+ INT prevStretchMode;
+ long centerWidth, centerHeight;
+ long sliceWidth, sliceHeight;
+
+ const BLENDFUNCTION blendFunction = { AC_SRC_OVER, 0, alphaConstant, AC_SRC_ALPHA };
+ const long targetWidth = RECTWIDTH(*targetRect);
+ const long targetHeight = RECTHEIGHT(*targetRect);
+ const long sourceWidth = RECTWIDTH(*sourceRect);
+ const long sourceHeight = RECTHEIGHT(*sourceRect);
+
+ if (NULL == targetDC || NULL == sourceDC)
+ return FALSE;
+
+ sliceWidth = sourceWidth/2;
+ sliceHeight = sourceHeight/2;
+
+ if (sliceWidth*2 > targetWidth)
+ sliceWidth = targetWidth/2;
+
+ if (sliceHeight*2 > targetHeight)
+ sliceHeight = targetHeight/2;
+
+ if (0 == sliceWidth || 0 == sliceHeight)
+ return FALSE;
+
+ prevStretchMode = SetStretchBltMode(targetDC, COLORONCOLOR);
+ SetViewportOrgEx(sourceDC, 0, 0, NULL);
+
+ GdiAlphaBlend(targetDC, targetRect->left, targetRect->top, sliceWidth, sliceHeight,
+ sourceDC, sourceRect->left, sourceRect->top, sliceWidth, sliceHeight, blendFunction);
+ GdiAlphaBlend(targetDC, targetRect->right - sliceWidth, targetRect->top, sliceWidth, sliceHeight,
+ sourceDC, sourceRect->right - sliceWidth, sourceRect->top, sliceWidth, sliceHeight, blendFunction);
+ GdiAlphaBlend(targetDC, targetRect->left, targetRect->bottom - sliceHeight, sliceWidth, sliceHeight,
+ sourceDC, sourceRect->left, sourceRect->bottom - sliceHeight, sliceWidth, sliceHeight, blendFunction);
+ GdiAlphaBlend(targetDC, targetRect->right - sliceWidth, targetRect->bottom - sliceHeight, sliceWidth, sliceHeight,
+ sourceDC, sourceRect->right - sliceWidth, sourceRect->bottom - sliceHeight, sliceWidth, sliceHeight, blendFunction);
+
+ if (targetWidth > 2*sliceWidth)
+ {
+ centerWidth = sourceWidth - 2*sliceWidth;
+ if(centerWidth < 1)
+ centerWidth = 1;
+
+ GdiAlphaBlend(targetDC, targetRect->left + sliceWidth, targetRect->top, targetWidth - (sliceWidth * 2), sliceHeight,
+ sourceDC, sourceRect->left + sliceWidth, sourceRect->top, centerWidth, sliceHeight, blendFunction);
+
+ GdiAlphaBlend(targetDC, targetRect->left + sliceWidth, targetRect->bottom - sliceHeight, targetWidth - (sliceWidth * 2), sliceHeight,
+ sourceDC, sourceRect->left + sliceWidth, sourceRect->bottom - sliceHeight, centerWidth, sliceHeight, blendFunction);
+ }
+ else
+ centerWidth = 0;
+
+ if (targetHeight > 2*sliceHeight)
+ {
+ centerHeight = sourceHeight - 2*sliceHeight;
+ if(centerHeight < 1)
+ centerHeight = 1;
+
+ GdiAlphaBlend(targetDC, targetRect->left, targetRect->top + sliceHeight, sliceWidth, targetHeight - (sliceHeight* 2),
+ sourceDC, sourceRect->left, sourceRect->top + sliceHeight, sliceWidth, centerHeight, blendFunction);
+
+ GdiAlphaBlend(targetDC, targetRect->right - sliceWidth, targetRect->top + sliceHeight, sliceWidth, targetHeight - (sliceHeight* 2),
+ sourceDC, sourceRect->right - sliceWidth, sourceRect->top + sliceHeight, sliceWidth, centerHeight, blendFunction);
+ }
+ else
+ centerHeight = 0;
+
+ if (FALSE != fillCenter &&
+ 0 != centerWidth && 0 != centerHeight)
+ {
+ GdiAlphaBlend(targetDC, targetRect->left + sliceWidth, targetRect->top + sliceHeight, targetWidth - (sliceWidth * 2), targetHeight - (sliceHeight* 2),
+ sourceDC, sourceRect->left + sliceWidth, sourceRect->top + sliceHeight, centerWidth, centerHeight, blendFunction);
+
+ }
+
+ SetStretchBltMode(targetDC, prevStretchMode);
+ return TRUE;
+}
+
+const ImageInfo *
+Image_GetBestFit(const ImageInfo *images, size_t count, unsigned int width, unsigned int height)
+{
+ const ImageInfo *image, *bestFit;
+ double widthDbl, heightDbl;
+ double scaleMin, scaleHorz, scaleVert;
+
+ if (NULL == images || count < 1)
+ return NULL;
+
+ if (width < 1)
+ width = 1;
+
+ if (height < 1)
+ height = 1;
+
+ widthDbl = width;
+ heightDbl = height;
+
+ image = &images[--count];
+ scaleHorz = widthDbl/image->width;
+ scaleVert = heightDbl/image->height;
+ scaleMin = (scaleHorz < scaleVert) ? scaleHorz : scaleVert;
+ bestFit = image;
+
+ if (1.0 != scaleMin)
+ {
+ scaleMin = fabs(1.0 - scaleMin);
+ while(count--)
+ {
+ image = &images[count];
+ scaleHorz = widthDbl/image->width;
+ scaleVert = heightDbl/image->height;
+ if (scaleHorz > scaleVert)
+ scaleHorz = scaleVert;
+
+ if (1.0 == scaleHorz)
+ {
+ bestFit = image;
+ break;
+ }
+
+ scaleHorz = fabs(1.0 - scaleHorz);
+ if (scaleHorz < scaleMin)
+ {
+ scaleMin = scaleHorz;
+ bestFit = image;
+ }
+ }
+ }
+
+ return bestFit;
+}
+
+BOOL
+Image_AlphaBlend(HDC targetDC, const RECT *targetRect,
+ HDC sourceDC, const RECT *sourceRect, BYTE sourceAlpha,
+ HBITMAP sourceBitmap, const RECT *paintRect, AlphaBlendFlags flags,
+ RECT *rectOut)
+{
+ BOOL result, clipSource;
+ RECT fillRect;
+ int sourceX, sourceY, sourceWidth, sourceHeight;
+ const BLENDFUNCTION blendFunction =
+ {
+ AC_SRC_OVER,
+ 0,
+ sourceAlpha,
+ AC_SRC_ALPHA
+ };
+
+ if (NULL != paintRect)
+ {
+ if (FALSE == IntersectRect(&fillRect, targetRect, paintRect))
+ return TRUE;
+
+ clipSource = TRUE;
+ }
+ else
+ {
+ CopyRect(&fillRect, targetRect);
+ clipSource = FALSE;
+ }
+
+ if (NULL != sourceRect)
+ {
+ sourceX = sourceRect->left;
+ sourceY = sourceRect->top;
+ sourceWidth = RECTWIDTH(*sourceRect);
+ sourceHeight = RECTHEIGHT(*sourceRect);
+ }
+ else
+ {
+ BITMAP bitmapInfo;
+ if (sizeof(bitmapInfo) != GetObject(sourceBitmap, sizeof(bitmapInfo), &bitmapInfo))
+ return FALSE;
+
+ sourceX = 0;
+ sourceY = 0;
+ sourceWidth = bitmapInfo.bmWidth;
+ sourceHeight = bitmapInfo.bmHeight;
+ if (sourceHeight < 0)
+ sourceHeight = -sourceHeight;
+ }
+
+ if (0 != (AlphaBlend_ScaleSource & flags))
+ {
+ RECT rect;
+ double scaleHorz, scaleVert;
+
+ scaleHorz = (double)RECTWIDTH(*targetRect) / sourceWidth;
+ scaleVert = (double)RECTHEIGHT(*targetRect) / sourceHeight;
+ if (scaleHorz > scaleVert)
+ scaleHorz = scaleVert;
+
+ SetRect(&rect, 0, 0, (int)(sourceWidth * scaleHorz), (int)(sourceHeight * scaleHorz));
+
+ if (0 != (AlphaBlend_AlignLeft & flags))
+ rect.left = targetRect->left;
+ else if (0 != (AlphaBlend_AlignRight & flags))
+ rect.left = targetRect->right - rect.right;
+ else
+ rect.left = targetRect->left + (RECTWIDTH(*targetRect) - rect.right)/2;
+
+ if (0 != (AlphaBlend_AlignTop & flags))
+ rect.top = targetRect->top;
+ else if (0 != (AlphaBlend_AlignBottom & flags))
+ rect.top = targetRect->bottom - rect.bottom;
+ else
+ rect.top = targetRect->top + (RECTHEIGHT(*targetRect) - rect.bottom)/2;
+
+ rect.right += rect.left;
+ rect.bottom += rect.top;
+
+ if (NULL != rectOut)
+ CopyRect(rectOut, &rect);
+
+ if (NULL != paintRect)
+ {
+ if (FALSE == IntersectRect(&fillRect, &rect, paintRect))
+ return TRUE;
+ }
+ else
+ CopyRect(&fillRect, &rect);
+
+ sourceX += (int)((fillRect.left - rect.left)/scaleHorz);
+ sourceY += (int)((fillRect.top - rect.top)/ scaleHorz);
+ sourceWidth -= (int)((RECTWIDTH(rect) - RECTWIDTH(fillRect))/scaleHorz);
+ sourceHeight -= (int)((RECTHEIGHT(rect) - RECTHEIGHT(fillRect))/scaleHorz);
+ clipSource = FALSE;
+ }
+ else
+ {
+ if (sourceWidth < RECTWIDTH(*targetRect) ||
+ sourceHeight < RECTHEIGHT(*targetRect))
+ {
+ RECT rect;
+
+ if (0 != (AlphaBlend_AlignLeft & flags))
+ rect.left = targetRect->left;
+ else if (0 != (AlphaBlend_AlignRight & flags))
+ rect.left = targetRect->right - sourceWidth;
+ else
+ rect.left = targetRect->left + (RECTWIDTH(*targetRect) - sourceWidth)/2;
+
+ if (0 != (AlphaBlend_AlignTop & flags))
+ rect.top = targetRect->top;
+ else if (0 != (AlphaBlend_AlignBottom & flags))
+ rect.top = targetRect->bottom - sourceHeight;
+ else
+ rect.top = targetRect->top + (RECTHEIGHT(*targetRect) - sourceHeight)/2;
+
+ rect.right = rect.left + sourceWidth;
+ rect.bottom = rect.top + sourceHeight;
+
+ if (NULL != paintRect)
+ {
+ if (FALSE == IntersectRect(&fillRect, &rect, paintRect))
+ return TRUE;
+
+ sourceX += (fillRect.left - rect.left);
+ sourceY += (fillRect.top - rect.top);
+ sourceWidth -= (RECTWIDTH(rect) - RECTWIDTH(fillRect));
+ sourceHeight -= (RECTHEIGHT(rect) - RECTHEIGHT(fillRect));
+ }
+ else
+ CopyRect(&fillRect, &rect);
+
+ if (NULL != rectOut)
+ CopyRect(rectOut, &rect);
+
+ clipSource = FALSE;
+ }
+ else if (NULL != rectOut)
+ CopyRect(rectOut, targetRect);
+ }
+
+
+
+ if (FALSE != clipSource)
+ {
+ sourceX += (fillRect.left - targetRect->left);
+ sourceY += (fillRect.top - targetRect->top);
+ sourceWidth -= (RECTWIDTH(*targetRect) - RECTWIDTH(fillRect));
+ sourceHeight -= (RECTHEIGHT(*targetRect) - RECTHEIGHT(fillRect));
+ }
+
+ if (NULL != sourceBitmap)
+ SelectBitmap(sourceDC, sourceBitmap);
+
+ result = GdiAlphaBlend(targetDC, fillRect.left, fillRect.top, RECTWIDTH(fillRect), RECTHEIGHT(fillRect),
+ sourceDC, sourceX, sourceY, sourceWidth, sourceHeight, blendFunction);
+
+ return result;
+
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/image.h b/Src/Plugins/Library/ml_devices/image.h
new file mode 100644
index 00000000..47871324
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/image.h
@@ -0,0 +1,227 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_IMAGE_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_IMAGE_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#define IMAGE_FILTER_NORMAL 0x00000000
+#define IMAGE_FILTER_GRAYSCALE 0x00010000
+#define IMAGE_FILTER_BLEND 0x00020000
+#define IMAGE_FILTER_MASK 0xFFFF0000
+
+
+typedef struct ImageInfo
+{
+ unsigned int width;
+ unsigned int height;
+ const wchar_t *path;
+} ImageInfo;
+
+
+HBITMAP
+Image_Load(const wchar_t *path,
+ unsigned int type,
+ unsigned int flags,
+ int width,
+ int height);
+
+HBITMAP
+Image_LoadEx(HINSTANCE instance,
+ const wchar_t *path,
+ unsigned int type,
+ unsigned int flags,
+ int width,
+ int height);
+
+HBITMAP
+Image_LoadSkinned(const wchar_t *path,
+ unsigned int type,
+ unsigned int flags, //ISF_XXX + IMAGE_FILTER_XXX
+ int width,
+ int height,
+ COLORREF backColor,
+ COLORREF frontColor,
+ COLORREF blendColor); // only valid if IMAGE_FILTER_BLEND set
+
+HBITMAP
+Image_LoadSkinnedEx(HINSTANCE instance,
+ const wchar_t *path,
+ unsigned int type,
+ unsigned int flags, //ISF_XXX + IMAGE_FILTER_XXX
+ int width,
+ int height,
+ COLORREF backColor,
+ COLORREF frontColor,
+ COLORREF blendColor); // only valid if IMAGE_FILTER_BLEND set
+
+BOOL
+Image_FilterEx(void *pixelData,
+ long width,
+ long height,
+ unsigned short bpp,
+ unsigned int flags,
+ COLORREF backColor,
+ COLORREF frontColor,
+ COLORREF blendColor);
+
+BOOL
+Image_Filter(HBITMAP bitmap,
+ unsigned int flags,
+ COLORREF backColor,
+ COLORREF frontColor,
+ COLORREF blendColor);
+
+BOOL
+Image_BlendEx(void *pixelData,
+ long width,
+ long height,
+ unsigned short bpp,
+ COLORREF blendColor);
+BOOL
+Image_Blend(HBITMAP bitmap,
+ COLORREF blendColor);
+
+HBITMAP
+Image_DuplicateDib(HBITMAP source);
+
+BOOL
+Image_ColorOver(HBITMAP hbmp,
+ const RECT *prcPart,
+ BOOL premult,
+ COLORREF rgb);
+
+BOOL
+Image_ColorOverEx(unsigned char *pPixels,
+ int bitmapCX,
+ int bitmapCY,
+ long x,
+ long y,
+ long cx,
+ long cy,
+ unsigned short bpp,
+ BOOL premult,
+ COLORREF rgb);
+
+BOOL
+Image_Premultiply(HBITMAP hbmp,
+ const RECT *prcPart);
+
+BOOL
+Image_PremultiplyEx(unsigned char *pPixels,
+ int bitmapCX,
+ int bitmapCY,
+ long x,
+ long y,
+ long cx,
+ long cy,
+ unsigned short bpp);
+
+BOOL
+Image_Demultiply(HBITMAP hbmp,
+ const RECT *prcPart);
+
+BOOL
+Image_DemultiplyEx(unsigned char *pPixels,
+ int bitmapCX,
+ int bitmapCY,
+ long x,
+ long y,
+ long cx,
+ long cy,
+ unsigned short bpp);
+
+BOOL
+Image_Saturate(HBITMAP hbmp,
+ const RECT *prcPart,
+ int n,
+ BOOL fScale);
+
+BOOL
+Image_SaturateEx(unsigned char *pPixels,
+ int bitmapCX,
+ int bitmapCY,
+ long x,
+ long y,
+ long cx,
+ long cy,
+ unsigned short bpp,
+ int n,
+ BOOL fScale);
+
+BOOL
+Image_AdjustAlpha(HBITMAP hbmp,
+ const RECT *prcPart,
+ int n,
+ BOOL fScale);
+
+BOOL
+Image_AdjustAlphaEx(unsigned char *pPixels,
+ int bitmapCX,
+ int bitmapCY,
+ long x,
+ long y,
+ long cx,
+ long cy,
+ unsigned short bpp,
+ int n,
+ BOOL fScale);
+
+BOOL
+Image_AdjustSaturationAlpha(HBITMAP hbmp,
+ const RECT *prcPart,
+ int nSaturation,
+ int nAlpha);
+
+BOOL
+Image_AdjustSaturationAlphaEx(unsigned char *pPixels,
+ int bitmapCX,
+ int bitmapCY,
+ long x,
+ long y,
+ long cx,
+ long cy,
+ unsigned short bpp,
+ int nSaturation,
+ int nAlpha);
+
+BOOL
+Image_FillBorder(HDC targetDC,
+ const RECT *targetRect,
+ HDC sourceDC,
+ const RECT *sourceRect,
+ BOOL fillCenter,
+ BYTE alphaConstant);
+
+
+const ImageInfo *
+Image_GetBestFit(const ImageInfo *images,
+ size_t count,
+ unsigned int width,
+ unsigned int height);
+
+typedef enum AlphaBlendFlags
+{
+ AlphaBlend_Normal = 0,
+ AlphaBlend_ScaleSource = (1 << 0),
+ AlphaBlend_AlignLeft = (1 << 1),
+ AlphaBlend_AlignRight = (1 << 2),
+ AlphaBlend_AlignCenter = 0,
+ AlphaBlend_AlignTop = (1 << 3),
+ AlphaBlend_AlignBottom = (1 << 4),
+ AlphaBlend_AlignVCenter = 0,
+}AlphaBlendFlags;
+DEFINE_ENUM_FLAG_OPERATORS(AlphaBlendFlags);
+
+BOOL
+Image_AlphaBlend(HDC targetDC,
+ const RECT *targetRect,
+ HDC sourceDC,
+ const RECT *sourceRect,
+ BYTE sourceAlpha,
+ HBITMAP sourceBitmap,
+ const RECT *paintRect,
+ AlphaBlendFlags flags,
+ RECT *rectOut);
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_IMAGE_HEADER
diff --git a/Src/Plugins/Library/ml_devices/imageCache.cpp b/Src/Plugins/Library/ml_devices/imageCache.cpp
new file mode 100644
index 00000000..52e745b7
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/imageCache.cpp
@@ -0,0 +1,902 @@
+#include "main.h"
+#include "./imageCache.h"
+#include <wincodec.h>
+#include <vector>
+#include <algorithm>
+
+
+struct DeviceColoredImage
+{
+ size_t ref;
+ DeviceImage *base;
+ HBITMAP bitmap;
+ COLORREF color1;
+ COLORREF color2;
+ DeviceImageFilter filter;
+ void *filterParam;
+};
+
+typedef std::vector<DeviceColoredImage*> DeviceColoredImageList;
+
+struct DeviceImage
+{
+ size_t ref;
+ DeviceImageCache *cache;
+ wchar_t *source;
+ HBITMAP bitmap;
+ HBITMAP exactBitmap;
+ int width;
+ int height;
+ DeviceImageLoader loader;
+ void *loaderParam;
+ DeviceColoredImageList list;
+};
+typedef std::vector<DeviceImage*> DeviceImageList;
+
+struct DeviceImageCache
+{
+ DeviceImageList list;
+ IWICImagingFactory *wicFactory;
+};
+
+typedef struct DeviceImageSeachParam
+{
+ const wchar_t *path;
+ int width;
+ int height;
+} DeviceImageSeachParam;
+
+typedef struct DeviceColoredImageSeachParam
+{
+ COLORREF color1;
+ COLORREF color2;
+} DeviceColoredImageSeachParam;
+
+
+DeviceImageCache *
+DeviceImageCache_Create()
+{
+ DeviceImageCache *self;
+
+ self = new DeviceImageCache();
+ if (NULL == self)
+ return NULL;
+
+ self->wicFactory = NULL;
+
+ return self;
+}
+
+
+void
+DeviceImageCache_Free(DeviceImageCache *self)
+{
+ if (NULL == self)
+ return;
+
+ size_t index = self->list.size();
+ while(index--)
+ {
+ DeviceImage *image = self->list[index];
+ image->cache = NULL;
+ }
+
+ if (NULL != self->wicFactory)
+ self->wicFactory->Release();
+
+ delete(self);
+}
+
+
+static DeviceImage *
+DeviceImage_Create(DeviceImageCache *cache, const wchar_t *path, int width, int height,
+ DeviceImageLoader loader, void *loaderParam)
+{
+ DeviceImage *self;
+
+ if (NULL == loader)
+ return NULL;
+
+ self = new DeviceImage();
+ if (NULL == self)
+ return NULL;
+
+ self->ref = 1;
+ self->cache = cache;
+ self->source = ResourceString_Duplicate(path);
+ self->loader = loader;
+ self->loaderParam = loaderParam;
+ self->bitmap = NULL;
+ self->exactBitmap = NULL;
+ self->width = width;
+ self->height = height;
+
+ return self;
+}
+
+static void
+DeviceImage_Free(DeviceImage *self)
+{
+ if (NULL == self)
+ return;
+
+ if (NULL != self->source)
+ ResourceString_Free(self->source);
+
+ if (NULL != self->bitmap)
+ DeleteObject(self->bitmap);
+
+ if (NULL != self->exactBitmap &&
+ self->exactBitmap != self->bitmap)
+ {
+ DeleteObject(self->exactBitmap);
+ }
+
+ delete(self);
+}
+
+static int
+DeviceImageCache_SearchCb(const void *key, const void *element)
+{
+ DeviceImageSeachParam *search;
+ DeviceImage *image;
+ int result;
+
+ search = (DeviceImageSeachParam*)key;
+ image = (DeviceImage*)element;
+
+ result = search->height - image->height;
+ if (0 != result)
+ return result;
+
+ result = search->width - image->width;
+ if (0 != result)
+ return result;
+
+ if (FALSE != IS_INTRESOURCE(search->path) ||
+ FALSE != IS_INTRESOURCE(image->source))
+ {
+ return (int)(INT_PTR)(search->path - image->source);
+ }
+
+ return CompareString(CSTR_INVARIANT, 0, search->path, -1, image->source, -1) - 2;
+}
+
+static int
+DeviceImageCache_SortCb(const void *element1, const void *element2)
+{
+ DeviceImage *image1;
+ DeviceImage *image2;
+ int result;
+
+ image1 = (DeviceImage*)element1;
+ image2 = (DeviceImage*)element2;
+
+ result = image1->height - image2->height;
+ if (0 != result)
+ return result;
+
+ result = image1->width - image2->width;
+ if (0 != result)
+ return result;
+
+ if (FALSE != IS_INTRESOURCE(image1->source) ||
+ FALSE != IS_INTRESOURCE(image2->source))
+ {
+ return (int)(INT_PTR)(image1->source - image2->source);
+ }
+
+ return CompareString(CSTR_INVARIANT, 0, image1->source, -1, image2->source, -1) - 2;
+}
+static int
+DeviceImageCache_SortCb_V2(const void* element1, const void* element2)
+{
+ return DeviceImageCache_SortCb(element1, element2) < 0;
+}
+
+
+static HBITMAP
+DeviceImage_DefaultImageLoader(const wchar_t *path, int width, int height, void *param)
+{
+ HBITMAP bitmap;
+ unsigned int flags;
+
+ flags = ISF_PREMULTIPLY;
+ if (FALSE == IS_INTRESOURCE(path))
+ flags |= ISF_LOADFROMFILE;
+
+ bitmap = Image_Load(path, SRC_TYPE_PNG, flags, 0, 0);
+
+ return bitmap;
+}
+
+DeviceImage *
+DeviceImageCache_GetImage(DeviceImageCache *self, const wchar_t *path, int width, int height, DeviceImageLoader loader, void *user)
+{
+ DeviceImage *image, *image_ptr = 0;
+ DeviceImageSeachParam searchParam;
+
+ if (width < 1)
+ width = 0;
+
+ if (height < 1)
+ height = 0;
+
+ if (NULL == self)
+ return NULL;
+
+ if (NULL == path ||
+ (FALSE == IS_INTRESOURCE(path) && L'\0' == *path))
+ {
+ return NULL;
+ }
+
+ searchParam.height = height;
+ searchParam.width = width;
+ searchParam.path = path;
+
+ //image_ptr = (DeviceImage**)bsearch(&searchParam, &self->list[0], self->list.size(),
+ // sizeof(DeviceImage**),
+ // DeviceImageCache_SearchCb);
+ auto it = std::find_if(self->list.begin(), self->list.end(),
+ [&](DeviceImage* upT) -> bool
+ {
+ return DeviceImageCache_SearchCb(&searchParam, upT) == 0;
+ }
+ );
+ if (it != self->list.end())
+ {
+ image_ptr = *it;
+ }
+
+
+ if (NULL != image_ptr)
+ {
+ image = image_ptr;
+ DeviceImage_AddRef(image);
+ return image;
+ }
+
+ if (NULL == loader)
+ loader = DeviceImage_DefaultImageLoader;
+
+ image = DeviceImage_Create(self, path, width, height, loader, user);
+ if (NULL != image)
+ {
+ self->list.push_back(image);
+ //qsort(&self->list[0], self->list.size(), sizeof(DeviceImage**), DeviceImageCache_SortCb);
+ std::sort(self->list.begin(), self->list.end(), DeviceImageCache_SortCb_V2);
+ }
+
+ return image;
+}
+
+static BOOL
+DeviceImageCache_RemoveImage(DeviceImageCache *self, DeviceImage *image)
+{
+ size_t index;
+
+ if (NULL == self || NULL == image)
+ return FALSE;
+
+ index = self->list.size();
+ while(index--)
+ {
+ if (self->list[index] == image)
+ {
+ self->list.erase(self->list.begin() + index);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+size_t
+DeviceImage_AddRef(DeviceImage *self)
+{
+ if (NULL == self)
+ return 0;
+ return InterlockedIncrement((LONG*)&self->ref);
+}
+
+size_t
+DeviceImage_Release(DeviceImage *self)
+{
+ size_t r;
+ if (NULL == self || 0 == self->ref)
+ return 0;
+
+ r = InterlockedDecrement((LONG*)&self->ref);
+ if (0 == r)
+ {
+ if (NULL != self->cache)
+ DeviceImageCache_RemoveImage(self->cache, self);
+ DeviceImage_Free(self);
+ return 0;
+ }
+
+ return r;
+}
+
+BOOL
+DeviceImage_GetSize(DeviceImage *self, int *width, int *height)
+{
+ if (NULL == self)
+ return FALSE;
+
+ if (NULL != width)
+ *width = self->width;
+
+ if (NULL != height)
+ *height = self->height;
+
+ return TRUE;
+}
+
+static IWICImagingFactory*
+DeviceImage_GetWicFactory(DeviceImageCache *cache)
+{
+ IWICImagingFactory *wicFactory;
+ HRESULT hr;
+
+ if (NULL != cache &&
+ NULL != cache->wicFactory)
+ {
+ cache->wicFactory->AddRef();
+ return cache->wicFactory;
+ }
+
+ hr = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(&wicFactory));
+
+ if (FAILED(hr))
+ return NULL;
+
+ if (NULL != cache)
+ {
+ wicFactory->AddRef();
+ cache->wicFactory = wicFactory;
+ }
+
+ return wicFactory;
+}
+
+
+static HBITMAP
+DeviceImage_HBitmapFromWicSource(IWICBitmapSource *wicSource, unsigned int targetWidth,
+ unsigned int targetHeight, DeviceImageFlags flags)
+{
+ HRESULT hr;
+ HBITMAP bitmap;
+ BITMAPINFO bitmapInfo;
+ unsigned int width, height, bitmapWidth, bitmapHeight;
+ void *pixelData = NULL;
+ WICPixelFormatGUID pixelFormat;
+ HDC windowDC;
+ unsigned int strideSize, imageSize;
+
+ if (NULL == wicSource)
+ return NULL;
+
+ hr = wicSource->GetPixelFormat(&pixelFormat);
+ if (FAILED(hr) ||
+ (GUID_WICPixelFormat32bppPBGRA != pixelFormat &&
+ GUID_WICPixelFormat32bppBGR != pixelFormat &&
+ GUID_WICPixelFormat32bppBGRA != pixelFormat &&
+ GUID_WICPixelFormat32bppRGBA != pixelFormat &&
+ GUID_WICPixelFormat32bppPRGBA != pixelFormat))
+ {
+ return NULL;
+ }
+
+ hr = wicSource->GetSize(&width, &height);
+ if (FAILED(hr))
+ return NULL;
+
+ if (0 != (DeviceImage_ExactSize & flags))
+ {
+ bitmapWidth = (targetWidth > width) ? targetWidth : width;
+ bitmapHeight = (targetHeight > height) ? targetHeight : height;
+ }
+ else
+ {
+ bitmapWidth = width;
+ bitmapHeight = height;
+ }
+
+ ZeroMemory(&bitmapInfo, sizeof(bitmapInfo));
+ bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ bitmapInfo.bmiHeader.biWidth = bitmapWidth;
+ bitmapInfo.bmiHeader.biHeight = -(LONG)bitmapHeight;
+ bitmapInfo.bmiHeader.biPlanes = 1;
+ bitmapInfo.bmiHeader.biBitCount = 32;
+ bitmapInfo.bmiHeader.biCompression = BI_RGB;
+
+ windowDC = GetDCEx(NULL, NULL, DCX_WINDOW | DCX_CACHE);
+
+ bitmap = CreateDIBSection(windowDC, &bitmapInfo, DIB_RGB_COLORS, &pixelData, NULL, 0);
+
+ if (NULL != windowDC)
+ ReleaseDC(NULL, windowDC);
+
+ if (NULL == bitmap)
+ return NULL;
+
+ hr = UIntMult(bitmapWidth, sizeof(DWORD), &strideSize);
+ if (SUCCEEDED(hr))
+ {
+ if (0 != (DeviceImage_ExactSize & flags) &&
+ (bitmapWidth > width || bitmapHeight > height))
+ {
+ if (SUCCEEDED(UIntMult(strideSize, bitmapHeight, &imageSize)))
+ ZeroMemory(pixelData, imageSize);
+ }
+
+ hr = UIntMult(strideSize, height, &imageSize);
+ if (SUCCEEDED(hr))
+ {
+ unsigned int offset, delta;
+
+ offset = 0;
+
+ if (0 != (DeviceImage_AlignVCenter & flags))
+ {
+ delta = bitmapHeight - height;
+ delta = delta/2 + delta%2;
+ }
+ else if (0 != (DeviceImage_AlignBottom & flags))
+ delta = bitmapHeight - height;
+ else
+ delta = 0;
+
+ if (0 != delta && SUCCEEDED(UIntMult(delta, strideSize, &delta)))
+ offset += delta;
+
+ if (0 != (DeviceImage_AlignHCenter & flags))
+ {
+ delta = bitmapWidth - width;
+ delta = delta/2;
+ }
+ else if (0 != (DeviceImage_AlignRight & flags))
+ delta = bitmapWidth - width;
+ else
+ delta = 0;
+
+ if (0 != delta && SUCCEEDED(UIntMult(delta, sizeof(DWORD), &delta)))
+ offset += delta;
+
+ hr = wicSource->CopyPixels(NULL, strideSize, imageSize, ((BYTE*)pixelData) + offset);
+ }
+ }
+
+ if (FAILED(hr))
+ {
+ DeleteObject(bitmap);
+ bitmap = NULL;
+ }
+
+ return bitmap;
+}
+
+static BOOL
+DeviceImage_ScaleBitmap(HBITMAP sourceBitmap, int width, int height, DeviceImageCache *cache,
+ DeviceImageFlags flags, HBITMAP *scalledBitmap)
+{
+ HBITMAP resultBitmap;
+ BITMAP sourceInfo;
+ IWICImagingFactory *wicFactory;
+ unsigned int requestedWidth, requestedHeight;
+ double scale, scaleH;
+ int t;
+
+ if (NULL == sourceBitmap || NULL == scalledBitmap)
+ return FALSE;
+
+ if (sizeof(sourceInfo) != GetObject(sourceBitmap, sizeof(sourceInfo), &sourceInfo))
+ return FALSE;
+
+ if (sourceInfo.bmHeight < 0)
+ sourceInfo.bmHeight = -sourceInfo.bmHeight;
+
+ scale = (double)width / sourceInfo.bmWidth;
+ scaleH = (double)height / sourceInfo.bmHeight;
+
+ if (scale > scaleH)
+ scale = scaleH;
+
+ if (1.0 == scale)
+ {
+ *scalledBitmap = NULL;
+ return TRUE;
+ }
+
+ requestedWidth = width;
+ requestedHeight = height;
+
+ t = (int)((sourceInfo.bmWidth * scale) + 0.5);
+ if (t < width)
+ width = t;
+
+ t = (int)((sourceInfo.bmHeight * scale) + 0.5);
+ if (t < height)
+ height = t;
+
+
+ resultBitmap = NULL;
+
+ wicFactory = DeviceImage_GetWicFactory(cache);
+ if (NULL != wicFactory)
+ {
+ HRESULT hr;
+ IWICBitmap *wicBitmap;
+
+ hr = wicFactory->CreateBitmapFromHBITMAP(sourceBitmap, NULL,
+ WICBitmapUsePremultipliedAlpha, &wicBitmap);
+ if (SUCCEEDED(hr))
+ {
+ IWICBitmapScaler *wicScaler;
+ hr = wicFactory->CreateBitmapScaler(&wicScaler);
+ if (SUCCEEDED(hr))
+ {
+ hr = wicScaler->Initialize(wicBitmap, width, height, WICBitmapInterpolationModeFant);
+ if (SUCCEEDED(hr))
+ {
+ resultBitmap = DeviceImage_HBitmapFromWicSource(wicScaler,
+ requestedWidth, requestedHeight, flags);
+ }
+ wicScaler->Release();
+
+ }
+ wicBitmap->Release();
+ }
+
+ wicFactory->Release();
+ }
+
+ *scalledBitmap = resultBitmap;
+ return (NULL != resultBitmap);
+}
+
+static HBITMAP
+DeviceImage_GetExactBitmap(DeviceImage *self, DeviceImageFlags flags)
+{
+ if (NULL == self)
+ return NULL;
+
+ if (NULL == self->exactBitmap)
+ {
+ if (NULL != self->bitmap)
+ {
+ BITMAP bi;
+ if (sizeof(bi) == GetObject(self->bitmap, sizeof(bi), &bi) &&
+ bi.bmWidth == self->width && bi.bmHeight == self->height)
+ {
+ self->exactBitmap = self->bitmap;
+ return self->exactBitmap;
+ }
+ }
+
+ if (NULL != self->loader)
+ {
+ HBITMAP bitmap;
+ bitmap = self->loader(self->source, self->width, self->height, self->loaderParam);
+ if (NULL != bitmap)
+ {
+ if (FALSE != DeviceImage_ScaleBitmap(bitmap, self->width, self->height,
+ self->cache, flags, &self->exactBitmap))
+ {
+
+ if (NULL == self->exactBitmap)
+ {
+ self->exactBitmap = bitmap;
+ bitmap = NULL;
+ }
+ }
+
+ if (NULL != bitmap)
+ DeleteObject(bitmap);
+ }
+ }
+ }
+
+ return self->exactBitmap;
+}
+
+
+HBITMAP
+DeviceImage_GetBitmap(DeviceImage *self, DeviceImageFlags flags)
+{
+ if (NULL == self)
+ return NULL;
+
+ if (0 != (DeviceImage_ExactSize & flags))
+ return DeviceImage_GetExactBitmap(self, flags);
+
+ if (NULL == self->bitmap)
+ {
+ if (NULL != self->loader)
+ {
+ HBITMAP bitmap;
+ bitmap = self->loader(self->source, self->width, self->height, self->loaderParam);
+ if (NULL != bitmap)
+ {
+ if (FALSE == DeviceImage_ScaleBitmap(bitmap, self->width, self->height,
+ self->cache, flags, &self->bitmap))
+ {
+ self->bitmap = NULL;
+ }
+
+ if (NULL != self->bitmap)
+ DeleteObject(bitmap);
+ else
+ self->bitmap = bitmap;
+ }
+ }
+ }
+
+ return self->bitmap;
+}
+
+
+static DeviceColoredImage *
+DeveiceColoredImage_Create(DeviceImage *base, COLORREF color1, COLORREF color2,
+ DeviceImageFilter filter, void *user)
+{
+ DeviceColoredImage *coloredImage;
+
+ if (NULL == base)
+ return NULL;
+
+ coloredImage = (DeviceColoredImage*)malloc(sizeof(DeviceColoredImage));
+ if (NULL == coloredImage)
+ return NULL;
+
+ ZeroMemory(coloredImage, sizeof(DeviceColoredImage));
+
+ coloredImage->ref = 1;
+ coloredImage->base = base;
+ coloredImage->color1 = color1;
+ coloredImage->color2 = color2;
+ coloredImage->filter = filter;
+ coloredImage->filterParam = user;
+
+ DeviceImage_AddRef(base);
+
+ return coloredImage;
+}
+
+static void
+DeviceColoredImage_Free(DeviceColoredImage *self)
+{
+ if (NULL == self)
+ return;
+
+ if (NULL != self->bitmap)
+ DeleteObject(self->bitmap);
+
+ free(self);
+}
+
+static int
+DeviceColoredImage_SearchCb(const void *key, const void *element)
+{
+ DeviceColoredImageSeachParam *search;
+ DeviceColoredImage *image;
+ int result;
+
+ search = (DeviceColoredImageSeachParam*)key;
+ image = (DeviceColoredImage*)element;
+
+ result = search->color1 - image->color1;
+ if (0 != result)
+ return result;
+
+ return search->color2- image->color2;
+
+}
+
+static int
+DeviceColoredImage_SortCb(const void *element1, const void *element2)
+{
+ DeviceColoredImage *image1;
+ DeviceColoredImage *image2;
+ int result;
+
+ image1 = (DeviceColoredImage*)element1;
+ image2 = (DeviceColoredImage*)element2;
+
+ result = image1->color1 - image2->color1;
+ if (0 != result)
+ return result;
+
+ return image1->color2- image2->color2;
+}
+
+static int
+DeviceColoredImage_SortCb_V2(const void* element1, const void* element2)
+{
+ return DeviceColoredImage_SortCb(element1, element2) < 0;
+}
+
+DeviceColoredImage *
+DeviceImage_GetColoredImage(DeviceImage *self, COLORREF color1, COLORREF color2,
+ DeviceImageFilter filter, void *user)
+{
+ size_t listSize;
+ DeviceColoredImage *image;
+ DeviceColoredImageSeachParam searchParam;
+
+ searchParam.color1 = color1;
+ searchParam.color2 = color2;
+
+ listSize = self->list.size();
+ if (listSize > 0)
+ {
+ DeviceColoredImage* image_ptr = NULL;
+ //DeviceColoredImage **image_ptr = (DeviceColoredImage**)bsearch(&searchParam, &self->list[0], listSize,
+ // sizeof(DeviceColoredImage**),
+ // DeviceColoredImage_SearchCb);
+
+ auto it = std::find_if(self->list.begin(), self->list.end(),
+ [&](DeviceColoredImage* upT) -> bool
+ {
+ return DeviceColoredImage_SearchCb(&searchParam, upT) == 0;
+ }
+ );
+ if (it != self->list.end())
+ {
+ image_ptr = *it;
+ }
+
+ if (NULL != image_ptr)
+ {
+ image = image_ptr;
+ DeviceColoredImage_AddRef(image);
+ return image;
+ }
+ }
+
+ image = DeveiceColoredImage_Create(self, color1, color2, filter, user);
+ if (NULL == image)
+ return NULL;
+
+ self->list.push_back(image);
+ listSize++;
+
+ if (listSize > 1)
+ {
+ //qsort(&self->list[0], self->list.size(), sizeof(DeviceColoredImage**),
+ // DeviceColoredImage_SortCb);
+ std::sort(self->list.begin(), self->list.end(), DeviceColoredImage_SortCb_V2);
+ }
+
+ return image;
+}
+
+static void
+DeviceImage_RemoveColored(DeviceImage *self, DeviceColoredImage *coloredImage)
+{
+ size_t index;
+
+ if (NULL == self || NULL == coloredImage)
+ return;
+
+ index = self->list.size();
+ while(index--)
+ {
+ if (coloredImage == self->list[index])
+ {
+ self->list.erase(self->list.begin() + index);
+ }
+ }
+}
+
+size_t
+DeviceColoredImage_AddRef(DeviceColoredImage *self)
+{
+ if (NULL == self)
+ return 0;
+ return InterlockedIncrement((LONG*)&self->ref);
+}
+
+size_t
+DeviceColoredImage_Release(DeviceColoredImage *self)
+{
+ size_t r;
+ if (NULL == self || 0 == self->ref)
+ return 0;
+
+ r = InterlockedDecrement((LONG*)&self->ref);
+ if (0 == r)
+ {
+ if (NULL != self->base)
+ {
+ DeviceImage_RemoveColored(self->base, self);
+ DeviceImage_Release(self->base);
+ }
+ DeviceColoredImage_Free(self);
+ return 0;
+ }
+
+ return r;
+}
+
+static BOOL
+DeviceColoredImage_DefaultFilter(HBITMAP bitmap, COLORREF color1, COLORREF color2, void *user)
+{
+ DIBSECTION bitmapDib;
+ BITMAP *bitmapInfo;
+ MLIMAGEFILTERAPPLYEX filter;
+
+ if (sizeof(bitmapDib) != GetObjectW(bitmap, sizeof(bitmapDib), &bitmapDib))
+ return FALSE;
+
+ bitmapInfo = &bitmapDib.dsBm;
+
+ filter.cbSize = sizeof(filter);
+ filter.pData = (BYTE*)bitmapInfo->bmBits;
+ filter.cx = bitmapInfo->bmWidth;
+ filter.cy = bitmapInfo->bmHeight;
+ filter.bpp = bitmapInfo->bmBitsPixel;
+ filter.imageTag = NULL;
+
+ filter.filterUID = MLIF_GRAYSCALE_UID;
+ MLImageFilter_ApplyEx(Plugin_GetLibraryWindow(), &filter);
+
+ filter.rgbBk = color1;
+ filter.rgbFg = color2;
+
+ if (32 == bitmapInfo->bmBitsPixel)
+ {
+ filter.filterUID = MLIF_FILTER1_PRESERVE_ALPHA_UID;
+ MLImageFilter_ApplyEx(Plugin_GetLibraryWindow(), &filter);
+ }
+ else
+ {
+ filter.filterUID = MLIF_FILTER1_UID;
+ MLImageFilter_ApplyEx(Plugin_GetLibraryWindow(), &filter);
+ }
+
+ return TRUE;
+}
+
+HBITMAP
+DeviceColoredImage_GetBitmap(DeviceColoredImage *self, DeviceImageFlags flags)
+{
+ if (NULL == self)
+ return NULL;
+
+ if (NULL == self->bitmap)
+ {
+ HBITMAP bitmap;
+ bitmap = DeviceImage_GetBitmap(self->base, flags);
+ if (NULL != bitmap)
+ {
+ self->bitmap = Image_DuplicateDib(bitmap);
+ if (NULL != self->bitmap)
+ {
+ DeviceImageFilter filter;
+ if (NULL == self->filter)
+ filter = DeviceColoredImage_DefaultFilter;
+ else
+ filter = self->filter;
+
+ Image_Demultiply(self->bitmap, NULL);
+ filter(self->bitmap, self->color1, self->color2, self->filterParam);
+ Image_Premultiply(self->bitmap, NULL);
+ }
+ }
+ }
+
+ return self->bitmap;
+}
+
+DeviceImage*
+DeviceColoredImage_GetBaseImage(DeviceColoredImage *self)
+{
+ if (NULL == self || NULL == self->base)
+ return NULL;
+
+ DeviceImage_AddRef(self->base);
+ return self->base;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/imageCache.h b/Src/Plugins/Library/ml_devices/imageCache.h
new file mode 100644
index 00000000..1c67f98b
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/imageCache.h
@@ -0,0 +1,76 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_IMAGE_CACHE_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_IMAGE_CACHE_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+typedef struct DeviceColoredImage DeviceColoredImage;
+typedef struct DeviceImage DeviceImage;
+typedef struct DeviceImageCache DeviceImageCache;
+
+typedef HBITMAP (*DeviceImageLoader)(const wchar_t* /*path*/, int /*width*/, int /*height*/, void* /*user*/);
+typedef BOOL (*DeviceImageFilter)(HBITMAP /*bitmap*/, COLORREF /*color1*/, COLORREF /*color2*/, void* /*user*/);
+
+DeviceImageCache *
+DeviceImageCache_Create();
+
+void
+DeviceImageCache_Free(DeviceImageCache *self);
+
+DeviceImage *
+DeviceImageCache_GetImage(DeviceImageCache *self,
+ const wchar_t *path,
+ int width,
+ int height,
+ DeviceImageLoader loader,
+ void *user);
+
+size_t
+DeviceImage_AddRef(DeviceImage *self);
+
+size_t
+DeviceImage_Release(DeviceImage *self);
+
+BOOL
+DeviceImage_GetSize(DeviceImage *self,
+ int *width,
+ int *height);
+
+typedef enum DeviceImageFlags
+{
+
+ DeviceImage_ExactSize = (1 << 0),
+ DeviceImage_AlignLeft = 0,
+ DeviceImage_AlignRight = (1 << 1),
+ DeviceImage_AlignHCenter = (1 << 2),
+ DeviceImage_AlignTop = 0,
+ DeviceImage_AlignBottom = (1 << 3),
+ DeviceImage_AlignVCenter = (1 << 4),
+ DeviceImage_Normal = (DeviceImage_AlignLeft | DeviceImage_AlignTop),
+} DeviceImageFlags;
+DEFINE_ENUM_FLAG_OPERATORS(DeviceImageFlags);
+
+HBITMAP
+DeviceImage_GetBitmap(DeviceImage *self, DeviceImageFlags flags);
+
+DeviceColoredImage *
+DeviceImage_GetColoredImage(DeviceImage *self,
+ COLORREF color1,
+ COLORREF color2,
+ DeviceImageFilter filter,
+ void *user);
+
+size_t
+DeviceColoredImage_AddRef(DeviceColoredImage *self);
+
+size_t
+DeviceColoredImage_Release(DeviceColoredImage *self);
+
+HBITMAP
+DeviceColoredImage_GetBitmap(DeviceColoredImage *self, DeviceImageFlags flags);
+
+DeviceImage*
+DeviceColoredImage_GetBaseImage(DeviceColoredImage *self);
+
+#endif // _NULLSOFT_WINAMP_ML_DEVICES_IMAGE_CACHE_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/infoWidget.cpp b/Src/Plugins/Library/ml_devices/infoWidget.cpp
new file mode 100644
index 00000000..7a85e9bc
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/infoWidget.cpp
@@ -0,0 +1,399 @@
+#include "main.h"
+#include "./infoWidget.h"
+
+#define INFOWIDGET_OFFSET_LEFT_DLU 4
+#define INFOWIDGET_OFFSET_TOP_DLU 2
+#define INFOWIDGET_OFFSET_RIGHT_DLU 4
+#define INFOWIDGET_OFFSET_BOTTOM_DLU 2
+
+#define INFOWIDGET_MIN_WIDTH_DLU (8*4)
+#define INFOWIDGET_MAX_WIDTH_DLU (48*4)
+
+#define INFOWIDGET_TITLE_OFFSET_BOTTOM_DLU 24
+#define INFOWIDGET_IMAGE_OFFSET_BOTTOM_DLU 24
+
+
+typedef struct InfoWidget
+{
+ wchar_t *title;
+ wchar_t *text;
+ wchar_t *imagePath;
+ HBITMAP image;
+ RECT titleRect;
+ RECT textRect;
+ RECT imageRect;
+ BackBuffer backBuffer;
+} InfoWidget;
+
+typedef struct InfoWidgetParam
+{
+ const wchar_t *title;
+ const wchar_t *text;
+ const wchar_t *imagePath;
+} InfoWidgetParam;
+
+static BOOL
+InfoWidget_InitCb(HWND hwnd, void **object, void *param)
+{
+ InfoWidget *self;
+ const InfoWidgetParam *createParam;
+
+ self = (InfoWidget*)malloc(sizeof(InfoWidget));
+ if (NULL == self)
+ return FALSE;
+
+ ZeroMemory(self, sizeof(InfoWidget));
+
+
+ createParam = (InfoWidgetParam*)param;
+
+ if (NULL != createParam)
+ {
+ wchar_t buffer[4096] = {0};
+
+ if (FALSE != IS_INTRESOURCE(createParam->title))
+ {
+ if (NULL != WASABI_API_LNG)
+ {
+ WASABI_API_LNGSTRINGW_BUF((int)(INT_PTR)createParam->title, buffer, ARRAYSIZE(buffer));
+ self->title = String_Duplicate(buffer);
+ }
+ else
+ self->title = NULL;
+ }
+ else
+ self->title = String_Duplicate(createParam->title);
+
+ if (FALSE != IS_INTRESOURCE(createParam->text))
+ {
+ if (NULL != WASABI_API_LNG)
+ {
+ WASABI_API_LNGSTRINGW_BUF((int)(INT_PTR)createParam->text, buffer, ARRAYSIZE(buffer));
+ self->text = String_Duplicate(buffer);
+ }
+ else
+ self->text = NULL;
+ }
+ else
+ self->text = String_Duplicate(createParam->text);
+
+ self->imagePath = ResourceString_Duplicate(createParam->imagePath);
+ }
+
+ BackBuffer_Initialize(&self->backBuffer, hwnd);
+
+ *object = self;
+
+ return TRUE;
+}
+
+static void
+InfoWidget_DestroyCb(InfoWidget *self, HWND hwnd)
+{
+ if (NULL == self)
+ return;
+
+ String_Free(self->title);
+ String_Free(self->text);
+ ResourceString_Free(self->imagePath);
+
+ if (NULL != self->image)
+ DeleteObject(self->image);
+
+ BackBuffer_Uninitialize(&self->backBuffer);
+ free(self);
+}
+
+
+static HBITMAP
+InfoWidget_GetImage(InfoWidget *self, WidgetStyle *style)
+{
+ if (NULL == self->image)
+ {
+ unsigned int flags;
+
+ flags = IMAGE_FILTER_BLEND;
+
+ if (FALSE == IS_INTRESOURCE(self->imagePath))
+ flags |= ISF_LOADFROMFILE;
+
+ self->image = Image_LoadSkinned(self->imagePath, SRC_TYPE_PNG, flags,
+ 0, 0,
+ WIDGETSTYLE_IMAGE_BACK_COLOR(style),
+ WIDGETSTYLE_IMAGE_FRONT_COLOR(style),
+ WIDGETSTYLE_BACK_COLOR(style));
+
+ }
+
+ return self->image;
+}
+
+static BOOL
+InfoWidget_GetImageSize(InfoWidget *self, WidgetStyle *style, SIZE *size)
+{
+ HBITMAP image;
+ BITMAP imageInfo;
+
+ image = InfoWidget_GetImage(self, style);
+ if (NULL == image)
+ return FALSE;
+
+ if (sizeof(imageInfo) != GetObject(image, sizeof(imageInfo), &imageInfo))
+ return FALSE;
+
+ size->cx = imageInfo.bmWidth;
+ size->cy = imageInfo.bmHeight;
+ if (size->cy < 0)
+ size->cy = -size->cy;
+
+ return TRUE;
+}
+
+static BOOL
+InfoWidget_GetTextSize(HDC hdc, HFONT font, const wchar_t *text, long width,
+ unsigned int format, SIZE *size)
+{
+ RECT rect;
+ BOOL result;
+ HFONT prevFont;
+
+ if (FALSE != IS_STRING_EMPTY(text))
+ {
+ size->cx = 0;
+ size->cy = 0;
+ return TRUE;
+ }
+
+ prevFont = SelectFont(hdc, font);
+
+ SetRect(&rect, 0, 0, width, 0);
+ result = DrawText(hdc, text, -1, &rect, DT_CALCRECT | format);
+ if (FALSE != result)
+ {
+ size->cx = RECTWIDTH(rect);
+ size->cy = RECTHEIGHT(rect);
+ }
+
+ SelectFont(hdc, prevFont);
+
+ return result;
+}
+
+static long
+InfoWidget_GetClientWidth(WidgetStyle *style, long viewWidth)
+{
+ long test;
+
+ viewWidth -= (WIDGETSTYLE_DLU_TO_HORZ_PX(style, INFOWIDGET_OFFSET_LEFT_DLU) +
+ WIDGETSTYLE_DLU_TO_HORZ_PX(style, INFOWIDGET_OFFSET_RIGHT_DLU));
+
+ test = WIDGETSTYLE_DLU_TO_HORZ_PX(style, INFOWIDGET_MIN_WIDTH_DLU);
+ if (viewWidth < test)
+ return test;
+
+ test = WIDGETSTYLE_DLU_TO_HORZ_PX(style, INFOWIDGET_MAX_WIDTH_DLU);
+ if (viewWidth > test)
+ return test;
+
+ return viewWidth;
+}
+
+static void
+InfoWidget_LayoutCb(InfoWidget *self, HWND hwnd, WidgetStyle *style,
+ const RECT *clientRect, SIZE *viewSize, BOOL redraw)
+{
+ HDC windowDC;
+ LONG offsetX, offsetY;
+ SIZE widgetSize;
+ RECT offsetRect;
+
+ if (NULL == self || NULL == style)
+ return;
+
+ windowDC = GetDCEx(hwnd, NULL, DCX_CACHE | DCX_NORESETATTRS);
+ if (NULL == windowDC)
+ return;
+
+ offsetRect.left = WIDGETSTYLE_DLU_TO_HORZ_PX(style, INFOWIDGET_OFFSET_LEFT_DLU);
+ offsetRect.top = WIDGETSTYLE_DLU_TO_VERT_PX(style, INFOWIDGET_OFFSET_TOP_DLU);
+ offsetRect.right = WIDGETSTYLE_DLU_TO_HORZ_PX(style, INFOWIDGET_OFFSET_RIGHT_DLU);
+ offsetRect.bottom = WIDGETSTYLE_DLU_TO_VERT_PX(style, INFOWIDGET_OFFSET_BOTTOM_DLU);
+
+ widgetSize.cx = InfoWidget_GetClientWidth(style, RECTWIDTH(*clientRect));
+ widgetSize.cy = 0;
+
+ SetRectEmpty(&self->imageRect);
+ if (FALSE != InfoWidget_GetImageSize(self, style, ((SIZE*)&self->imageRect) + 1))
+ {
+ if (widgetSize.cx < self->imageRect.right)
+ widgetSize.cx = self->imageRect.right;
+
+ widgetSize.cy += self->imageRect.bottom;
+ if (0 != self->imageRect.bottom)
+ widgetSize.cy += WIDGETSTYLE_DLU_TO_VERT_PX(style, INFOWIDGET_IMAGE_OFFSET_BOTTOM_DLU);
+ }
+
+ SetRectEmpty(&self->titleRect);
+ if (FALSE != InfoWidget_GetTextSize(windowDC, style->titleFont, self->title, widgetSize.cx,
+ DT_LEFT | DT_TOP | DT_NOPREFIX | DT_WORDBREAK, ((SIZE*)&self->titleRect) + 1))
+ {
+ widgetSize.cy += self->titleRect.bottom;
+ if (0 != self->titleRect.bottom)
+ widgetSize.cy += WIDGETSTYLE_DLU_TO_VERT_PX(style, INFOWIDGET_TITLE_OFFSET_BOTTOM_DLU);
+ }
+
+ SetRectEmpty(&self->textRect);
+ if (FALSE != InfoWidget_GetTextSize(windowDC, style->textFont, self->text, widgetSize.cx,
+ DT_LEFT | DT_TOP | DT_NOPREFIX | DT_WORDBREAK, ((SIZE*)&self->textRect) + 1))
+ {
+ widgetSize.cy += self->textRect.bottom;
+ }
+
+
+ if ((widgetSize.cx + (offsetRect.left + offsetRect.right)) < RECTWIDTH(*clientRect))
+ offsetX = clientRect->left + (RECTWIDTH(*clientRect) - widgetSize.cx)/2;
+ else
+ offsetX = clientRect->left + offsetRect.left;
+
+ if ((widgetSize.cy + (offsetRect.top + offsetRect.bottom)) < RECTHEIGHT(*clientRect))
+ offsetY = clientRect->top + (RECTHEIGHT(*clientRect) - widgetSize.cy)/2;
+ else
+ offsetY = clientRect->top + offsetRect.top;
+
+
+ if (FALSE == IsRectEmpty(&self->titleRect))
+ {
+ OffsetRect(&self->titleRect, offsetX + (widgetSize.cx - self->titleRect.right)/2, offsetY);
+ offsetY = self->titleRect.bottom;
+ offsetY += WIDGETSTYLE_DLU_TO_VERT_PX(style, INFOWIDGET_TITLE_OFFSET_BOTTOM_DLU);
+ }
+
+ if (FALSE == IsRectEmpty(&self->imageRect))
+ {
+ OffsetRect(&self->imageRect, offsetX + (widgetSize.cx - self->imageRect.right)/2, offsetY);
+ offsetY = self->imageRect.bottom;
+ offsetY += WIDGETSTYLE_DLU_TO_VERT_PX(style, INFOWIDGET_IMAGE_OFFSET_BOTTOM_DLU);
+ }
+
+ if (FALSE == IsRectEmpty(&self->textRect))
+ {
+ OffsetRect(&self->textRect, offsetX + (widgetSize.cx - self->textRect.right)/2, offsetY);
+ }
+
+ ReleaseDC(hwnd, windowDC);
+
+ viewSize->cx = widgetSize.cx + offsetRect.left + offsetRect.right;
+ viewSize->cy = widgetSize.cy + offsetRect.top + offsetRect.bottom;
+}
+
+
+static BOOL
+InfoWidget_PaintCb(InfoWidget *self, HWND hwnd, WidgetStyle *style, HDC hdc, const RECT *paintRect, BOOL erase)
+{
+ RECT intersectRect;
+ FillRegion fillRegion;
+
+ FillRegion_Init(&fillRegion, paintRect);
+
+ if (FALSE == IS_STRING_EMPTY(self->title) &&
+ FALSE != IntersectRect(&intersectRect, &self->titleRect, paintRect))
+ {
+ if (FALSE != BackBuffer_DrawTextEx(&self->backBuffer, hdc, self->title, -1, &self->titleRect,
+ DT_CENTER | DT_NOPREFIX | DT_WORDBREAK,
+ style->titleFont, style->backColor, style->titleColor, OPAQUE))
+ {
+ FillRegion_ExcludeRect(&fillRegion, &intersectRect);
+ }
+ }
+
+ if (NULL != self->image &&
+ FALSE != IntersectRect(&intersectRect, &self->imageRect, paintRect))
+ {
+ HDC windowDC = GetDCEx(hwnd, NULL, DCX_CACHE | DCX_NORESETATTRS);
+ if (NULL != windowDC)
+ {
+ HDC sourceDC = CreateCompatibleDC(windowDC);
+ if (NULL != sourceDC)
+ {
+ HBITMAP prevBitmap = SelectBitmap(sourceDC, self->image);
+ if (FALSE != BitBlt(hdc, intersectRect.left, intersectRect.top,
+ RECTWIDTH(intersectRect), RECTHEIGHT(intersectRect),
+ sourceDC,
+ intersectRect.left - self->imageRect.left,
+ intersectRect.top - self->imageRect.top,
+ SRCCOPY))
+ {
+ FillRegion_ExcludeRect(&fillRegion, &intersectRect);
+ }
+
+ SelectBitmap(sourceDC, prevBitmap);
+ DeleteDC(sourceDC);
+ }
+ ReleaseDC(hwnd, windowDC);
+ }
+ }
+
+ if (FALSE == IS_STRING_EMPTY(self->text) &&
+ FALSE != IntersectRect(&intersectRect, &self->textRect, paintRect))
+ {
+ if (FALSE != BackBuffer_DrawTextEx(&self->backBuffer, hdc, self->text, -1, &self->textRect,
+ DT_CENTER | DT_NOPREFIX | DT_WORDBREAK,
+ style->textFont, style->backColor, style->textColor, OPAQUE))
+ {
+ FillRegion_ExcludeRect(&fillRegion, &intersectRect);
+ }
+ }
+
+
+ if (FALSE != erase)
+ FillRegion_BrushFill(&fillRegion, hdc, style->backBrush);
+
+ FillRegion_Uninit(&fillRegion);
+
+ return TRUE;
+}
+
+static void
+InfoWidget_StyleColorChangedCb(InfoWidget *self, HWND hwnd, WidgetStyle *style)
+{
+ if (NULL == self)
+ return;
+
+ if (NULL != self->image)
+ {
+ if (NULL != self->image)
+ DeleteObject(self->image);
+
+ self->image = NULL;
+ InfoWidget_GetImage(self, style);
+ }
+}
+
+HWND InfoWidget_CreateWindow(unsigned int type, const wchar_t *title, const wchar_t *text,
+ const wchar_t *imagePath, HWND parentWindow,
+ int x, int y, int width, int height, BOOL border, unsigned int controlId)
+{
+ const static WidgetInterface infoWidgetInterface =
+ {
+ (WidgetInitCallback)InfoWidget_InitCb,
+ (WidgetDestroyCallback)InfoWidget_DestroyCb,
+ (WidgetLayoutCallback)InfoWidget_LayoutCb,
+ (WidgetPaintCallback)InfoWidget_PaintCb,
+ (WidgetStyleCallback)InfoWidget_StyleColorChangedCb,
+ };
+
+ InfoWidgetParam param;
+
+ param.title = title;
+ param.text = text;
+ param.imagePath = imagePath;
+
+ return Widget_CreateWindow(type,
+ &infoWidgetInterface,
+ NULL,
+ (FALSE != border) ? WS_EX_CLIENTEDGE : 0,
+ 0,
+ x, y, width, height,
+ parentWindow,
+ controlId, &param);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/infoWidget.h b/Src/Plugins/Library/ml_devices/infoWidget.h
new file mode 100644
index 00000000..deb0035c
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/infoWidget.h
@@ -0,0 +1,27 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_INFO_WIDGET_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_INFO_WIDGET_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+#define WIDGET_TYPE_UNKNOWN 0
+#define WIDGET_TYPE_WELCOME 1
+#define WIDGET_TYPE_SERVICE_ERROR 2
+#define WIDGET_TYPE_VIEW_ERROR 3
+
+HWND InfoWidget_CreateWindow(unsigned int type,
+ const wchar_t *title,
+ const wchar_t *text,
+ const wchar_t *imagePath,
+ HWND parentWindow,
+ int x,
+ int y,
+ int width,
+ int height,
+ BOOL border,
+ unsigned int controlId);
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_INFO_WIDGET_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/listWidget.cpp b/Src/Plugins/Library/ml_devices/listWidget.cpp
new file mode 100644
index 00000000..7b57142d
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/listWidget.cpp
@@ -0,0 +1,4096 @@
+#include "main.h"
+#include "./listWidget.h"
+#include "./listWidgetInternal.h"
+#include <vector>
+
+#include <strsafe.h>
+
+#define LISTWIDGET_OFFSET_LEFT_DLU 0
+#define LISTWIDGET_OFFSET_TOP_DLU 1
+#define LISTWIDGET_OFFSET_BOTTOM_DLU 2
+
+#define LISTWIDGET_CATEGORY_OFFSET_TOP_DLU 0
+#define LISTWIDGET_CATEGORY_OFFSET_LEFT_DLU 1
+#define LISTWIDGET_CATEGORY_SPACING_DLU 2
+#define LISTWIDGET_CATEGORY_SPACING_COLLAPSED_DLU 0
+
+#define LISTWIDGET_ITEM_OFFSET_TOP_DLU 1
+#define LISTWIDGET_ITEM_OFFSET_LEFT_DLU 2
+#define LISTWIDGET_ITEM_SPACING_HORZ_DLU 2
+#define LISTWIDGET_ITEM_SPACING_VERT_DLU 10
+#define LISTWIDGET_ITEM_TITLE_MAX_LINES 3
+
+#define LISTWIDGET_IMAGE_MIN_HEIGHT 48
+#define LISTWIDGET_IMAGE_MAX_HEIGHT 256
+#define LISTWIDGET_IMAGE_DEFAULT_HEIGHT 160//128
+#define LISTWIDGET_IMAGE_DEFAULT_WIDTH 160//96
+
+#define LISTWIDGETTIMER_SHOW_COMMANDS_ID 3
+#define LISTWIDGETTIMER_SHOW_COMMANDS_DELAY 75
+#define LISTWIDGETTIMER_PROGRESS_TICK_ID 4
+#define LISTWIDGETTIMER_PROGRESS_TICK_DELAY 130
+#define LISTWIDGETTIMER_EDIT_TITLE_ID 5
+
+#define LISTWIDGET_CONNECTION_MIN_HEIGHT 20
+#define LISTWIDGET_CONNECTION_MAX_HEIGHT 48
+#define LISTWIDGET_CONNECTION_DEFAULT_HEIGHT 36
+
+#define LISTWIDGET_PRIMARYCOMMAND_MIN_HEIGHT 16
+#define LISTWIDGET_PRIMARYCOMMAND_MAX_HEIGHT 48
+#define LISTWIDGET_PRIMARYCOMMAND_DEFAULT_HEIGHT 36
+
+#define LISTWIDGET_SECONDARYCOMMAND_MIN_HEIGHT 14
+#define LISTWIDGET_SECONDARYCOMMAND_MAX_HEIGHT 36
+#define LISTWIDGET_SECONDARYCOMMAND_DEFAULT_HEIGHT 20
+
+#define LISTWIDGET_ACTIVITY_MIN_HEIGHT 16
+#define LISTWIDGET_ACTIVITY_MAX_HEIGHT 48
+#define LISTWIDGET_ACTIVITY_DEFAULT_HEIGHT 36
+
+#define LISTWIDGET_PROGRESS_MIN_HEIGHT 16
+
+#define LISTWIDGET_PROGRESS_FRAME_COUNT 9/*12*/
+
+typedef std::vector<ifc_device*> DeviceList;
+
+
+static ListWidgetCategory *
+ListWidget_CreateCategoryHelper(const char *name, int titleId, wchar_t *buffer, size_t bufferMax)
+{
+ WASABI_API_LNGSTRINGW_BUF(titleId, buffer, bufferMax);
+ return ListWidget_CreateCategory(name,
+ buffer,
+ Config_ReadBool("CollapsedCategories", name, FALSE));
+}
+
+static void
+ListWidget_CreateDefaultCategories(ListWidget *self)
+{
+ ListWidgetCategory *category;
+ wchar_t buffer[512] = {0};
+
+ category = ListWidget_CreateCategoryHelper("attached", IDS_CATEGORY_ATTACHED, buffer, ARRAYSIZE(buffer));
+ if (NULL != category)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_CATEGORY_ATTACHED_EMPTY_TEXT, buffer, ARRAYSIZE(buffer));
+ ListWidget_SetCategoryEmptyText(category, buffer);
+ self->categories.push_back(category);
+ }
+
+ category = ListWidget_CreateCategoryHelper("discovered", IDS_CATEGORY_DISCOVERED, buffer, ARRAYSIZE(buffer));
+ if (NULL != category)
+ self->categories.push_back(category);
+}
+
+BOOL
+ListWidget_GetViewOrigin(HWND hwnd, POINT *pt)
+{
+ SCROLLINFO scrollInfo;
+
+ if (NULL == pt)
+ return FALSE;
+
+ scrollInfo.cbSize = sizeof(scrollInfo);
+ scrollInfo.fMask = SIF_POS;
+
+ if (FALSE == GetScrollInfo(hwnd, SB_HORZ, &scrollInfo))
+ return FALSE;
+ pt->x = -scrollInfo.nPos;
+
+ if (FALSE == GetScrollInfo(hwnd, SB_VERT, &scrollInfo))
+ return FALSE;
+ pt->y = -scrollInfo.nPos;
+
+ return TRUE;
+}
+
+
+static HBITMAP
+ListWidget_CreateSpacebarBitmap(HBITMAP sourceBitmap, HWND hwnd, long width, long height)
+{
+ HDC windowDC, sourceDC, resultDC;
+ HBITMAP resultBitmap;
+ BITMAP sourceInfo;
+ RECT resultRect, sourceRect;
+
+ if (NULL == sourceBitmap ||
+ sizeof(sourceInfo) != GetObject(sourceBitmap, sizeof(sourceInfo), &sourceInfo))
+ {
+ return FALSE;
+ }
+
+ windowDC = GetDCEx(hwnd, NULL, DCX_CACHE | DCX_NORESETATTRS);
+ if (NULL == windowDC)
+ return NULL;
+
+ sourceDC = CreateCompatibleDC(windowDC);
+ resultDC = CreateCompatibleDC(windowDC);
+ resultBitmap = CreateCompatibleBitmap(windowDC, width, height*2);
+
+ ReleaseDC(hwnd, windowDC);
+
+ if (NULL != sourceDC &&
+ NULL != resultDC &&
+ NULL != resultBitmap)
+ {
+ HBITMAP prevSourceBitmap, prevResultBitmap;
+ prevSourceBitmap = SelectBitmap(sourceDC, sourceBitmap);
+ prevResultBitmap = SelectBitmap(resultDC, resultBitmap);
+
+
+ SetRect(&resultRect, 0, 0, width, height);
+ SetRect(&sourceRect, 0, 0, sourceInfo.bmWidth, ABS(sourceInfo.bmHeight)/2);
+
+ Image_FillBorder(resultDC, &resultRect, sourceDC, &sourceRect, TRUE, 255);
+
+ OffsetRect(&resultRect, 0, height);
+ OffsetRect(&sourceRect, 0, RECTHEIGHT(sourceRect));
+
+ Image_FillBorder(resultDC, &resultRect, sourceDC, &sourceRect, TRUE, 255);
+
+
+ SelectBitmap(sourceDC, prevSourceBitmap);
+ SelectBitmap(resultDC, prevResultBitmap);
+
+ }
+
+ if (NULL != sourceDC)
+ DeleteDC(sourceDC);
+ if (NULL != resultDC)
+ DeleteDC(resultDC);
+
+ if (NULL != resultBitmap)
+ {
+ RECT imageRect;
+ SetRect(&imageRect, 0, 0, width, height);
+ Image_Premultiply(resultBitmap, &imageRect);
+ }
+
+ return resultBitmap;
+}
+
+HBITMAP
+ListWidget_GetSpacebarBitmap(ListWidget *self, WidgetStyle *style, HWND hwnd, long width, long height)
+{
+
+ if (NULL == self)
+ return NULL;
+
+ if (NULL != self->spacebarBitmap)
+ {
+ BITMAP bi;
+ if (sizeof(bi) != GetObject(self->spacebarBitmap, sizeof(bi), &bi) ||
+ bi.bmWidth != width ||
+ bi.bmHeight/2 != height)
+ {
+ DeleteObject(self->spacebarBitmap);
+ self->spacebarBitmap = NULL;
+ }
+ }
+
+ if (NULL == self->spacebarBitmap &&
+ NULL != style)
+ {
+ HBITMAP baseBitmap;
+ baseBitmap = Image_Load(MAKEINTRESOURCE(IDR_SPACEBAR_IMAGE), SRC_TYPE_PNG, 0, 0, 0);
+
+ //WIDGETSTYLE_IMAGE_BACK_COLOR(style),
+ // WIDGETSTYLE_IMAGE_FRONT_COLOR(style),
+ // WIDGETSTYLE_BACK_COLOR(style));
+
+ if (NULL != baseBitmap)
+ {
+ DIBSECTION bitmapData;
+
+ if (sizeof(bitmapData) == GetObjectW(baseBitmap, sizeof(bitmapData), &bitmapData))
+ {
+ BITMAP *bi;
+ long bitmapHeight;
+ void *pixels;
+ WORD backHue, backLuma, backSat, frontHue, frontLuma, frontSat;
+
+ bi = &bitmapData.dsBm;
+ bitmapHeight = ABS(bi->bmHeight);
+
+ pixels = ((BYTE*)bi->bmBits) + (bi->bmWidthBytes * (bitmapHeight - bitmapHeight/2));
+
+ ColorRGBToHLS(WIDGETSTYLE_IMAGE_BACK_COLOR(style), &backHue, &backLuma, &backSat);
+ ColorRGBToHLS(WIDGETSTYLE_IMAGE_FRONT_COLOR(style), &frontHue, &frontLuma, &frontSat);
+
+ if (backLuma > frontLuma)
+ {
+ COLORREF backColor;
+
+ frontLuma = 25;
+ backColor = ColorHLSToRGB(frontHue, frontLuma, frontSat);
+
+ Image_FilterEx(pixels, bi->bmWidth, bitmapHeight/2, bi->bmBitsPixel,
+ 0,
+ backColor,
+ WIDGETSTYLE_IMAGE_BACK_COLOR(style),
+ WIDGETSTYLE_BACK_COLOR(style));
+
+ }
+ else
+ {
+ Image_FilterEx(pixels, bi->bmWidth, bitmapHeight/2, bi->bmBitsPixel,
+ 0,
+ WIDGETSTYLE_IMAGE_BACK_COLOR(style),
+ WIDGETSTYLE_IMAGE_FRONT_COLOR(style),
+ WIDGETSTYLE_BACK_COLOR(style));
+ }
+ }
+
+
+ Image_Premultiply(baseBitmap, NULL);
+ self->spacebarBitmap = ListWidget_CreateSpacebarBitmap(baseBitmap, hwnd, width, height);
+ DeleteObject(baseBitmap);
+ }
+ }
+
+ return self->spacebarBitmap;
+}
+
+static HBITMAP
+ListWidget_CreateBorderBitmap(HBITMAP sourceBitmap, HWND hwnd, long width, long height)
+{
+ HDC windowDC, sourceDC, resultDC;
+ HBITMAP resultBitmap;
+ BITMAP sourceInfo;
+ RECT resultRect, sourceRect;
+
+ if (NULL == sourceBitmap ||
+ sizeof(sourceInfo) != GetObject(sourceBitmap, sizeof(sourceInfo), &sourceInfo))
+ {
+ return FALSE;
+ }
+
+ SetRect(&resultRect, 0, 0, width, height);
+ SetRect(&sourceRect, 0, 0, sourceInfo.bmWidth, ABS(sourceInfo.bmHeight));
+
+ windowDC = GetDCEx(hwnd, NULL, DCX_CACHE | DCX_NORESETATTRS);
+ if (NULL == windowDC)
+ return NULL;
+
+ sourceDC = CreateCompatibleDC(windowDC);
+ resultDC = CreateCompatibleDC(windowDC);
+ resultBitmap = CreateCompatibleBitmap(windowDC, width, height);
+
+ ReleaseDC(hwnd, windowDC);
+
+ if (NULL != sourceDC &&
+ NULL != resultDC &&
+ NULL != resultBitmap)
+ {
+ HBITMAP prevSourceBitmap, prevResultBitmap;
+ prevSourceBitmap = SelectBitmap(sourceDC, sourceBitmap);
+ prevResultBitmap = SelectBitmap(resultDC, resultBitmap);
+
+ Image_FillBorder(resultDC, &resultRect, sourceDC, &sourceRect, TRUE, 255);
+
+ SelectBitmap(sourceDC, prevSourceBitmap);
+ SelectBitmap(resultDC, prevResultBitmap);
+
+ }
+
+ if (NULL != sourceDC)
+ DeleteDC(sourceDC);
+ if (NULL != resultDC)
+ DeleteDC(resultDC);
+
+ return resultBitmap;
+}
+
+static HBITMAP
+ListWidget_GetBorderBitmap(HBITMAP bitmap, const wchar_t *path, WidgetStyle *style,
+ HWND hwnd, long width, long height, BOOL disableSkin,
+ COLORREF colorBack, COLORREF colorFront)
+{
+
+ if (NULL != bitmap)
+ {
+ BITMAP bi;
+ if (sizeof(bi) != GetObject(bitmap, sizeof(bi), &bi) ||
+ bi.bmWidth != width ||
+ bi.bmHeight != height)
+ {
+ DeleteObject(bitmap);
+ bitmap = NULL;
+ }
+ }
+
+ if (NULL == bitmap &&
+ NULL != style)
+ {
+
+ HBITMAP baseBitmap;
+
+ if (FALSE == disableSkin)
+ {
+ baseBitmap = Image_LoadSkinned(path,
+ SRC_TYPE_PNG,
+ IMAGE_FILTER_NORMAL,
+ 0, 0,
+ colorBack, colorFront,
+ WIDGETSTYLE_BACK_COLOR(style));
+ }
+ else
+ {
+ baseBitmap = Image_Load(path, SRC_TYPE_PNG, 0, 0, 0);
+ }
+ if (NULL != baseBitmap)
+ {
+ Image_Premultiply(baseBitmap, NULL);
+ bitmap = ListWidget_CreateBorderBitmap(baseBitmap, hwnd, width, height);
+ DeleteObject(baseBitmap);
+ }
+ }
+
+ return bitmap;
+}
+HBITMAP
+ListWidget_GetHoverBitmap(ListWidget *self, WidgetStyle *style, HWND hwnd, long width, long height)
+{
+ if (NULL == self)
+ return NULL;
+
+ self->hoverBitmap = ListWidget_GetBorderBitmap(self->hoverBitmap,
+ MAKEINTRESOURCE(IDR_ITEM_HOVER_IMAGE),
+ style, hwnd, width, height, FALSE,
+ WIDGETSTYLE_SELECT_BACK_COLOR(style),
+ WIDGETSTYLE_SELECT_FRONT_COLOR(style));
+
+ return self->hoverBitmap;
+}
+
+HBITMAP
+ListWidget_GetSelectBitmap(ListWidget *self, WidgetStyle *style, HWND hwnd, long width, long height)
+{
+
+ if (NULL == self)
+ return NULL;
+
+ self->selectBitmap = ListWidget_GetBorderBitmap(self->selectBitmap,
+ MAKEINTRESOURCE(IDR_ITEM_SELECT_IMAGE),
+ style, hwnd, width, height, FALSE,
+ WIDGETSTYLE_SELECT_BACK_COLOR(style),
+ WIDGETSTYLE_SELECT_FRONT_COLOR(style));
+
+ return self->selectBitmap;
+}
+
+HBITMAP
+ListWidget_GetInactiveSelectBitmap(ListWidget *self, WidgetStyle *style, HWND hwnd, long width, long height)
+{
+
+ if (NULL == self)
+ return NULL;
+
+ self->inactiveSelectBitmap = ListWidget_GetBorderBitmap(self->inactiveSelectBitmap,
+ MAKEINTRESOURCE(IDR_ITEM_SELECT_IMAGE),
+ style, hwnd, width, height, FALSE,
+ WIDGETSTYLE_INACTIVE_SELECT_BACK_COLOR(style),
+ WIDGETSTYLE_INACTIVE_SELECT_FRONT_COLOR(style));
+
+ return self->inactiveSelectBitmap;
+}
+
+HBITMAP
+ListWidget_GetLargeBadgeBitmap(ListWidget *self, WidgetStyle *style, HWND hwnd, long width, long height)
+{
+
+ if (NULL == self)
+ return NULL;
+
+ self->largeBadgeBitmap = ListWidget_GetBorderBitmap(self->largeBadgeBitmap,
+ MAKEINTRESOURCE(IDR_COMMAND_BACKGROUND_IMAGE),
+ style, hwnd, width, height, TRUE,
+ WIDGETSTYLE_IMAGE_BACK_COLOR(style),
+ WIDGETSTYLE_IMAGE_FRONT_COLOR(style));
+
+ return self->largeBadgeBitmap;
+}
+
+HBITMAP
+ListWidget_GetSmallBadgeBitmap(ListWidget *self, WidgetStyle *style, HWND hwnd, long width, long height)
+{
+
+ if (NULL == self)
+ return NULL;
+
+ self->smallBadgeBitmap = ListWidget_GetBorderBitmap(self->smallBadgeBitmap,
+ MAKEINTRESOURCE(IDR_COMMAND_SECONDARY_BACKGROUND_IMAGE),
+ style, hwnd, width, height, TRUE,
+ WIDGETSTYLE_IMAGE_BACK_COLOR(style),
+ WIDGETSTYLE_IMAGE_FRONT_COLOR(style));
+
+ return self->smallBadgeBitmap;
+}
+
+HBITMAP
+ListWidget_GetArrowsBitmap(ListWidget *self, WidgetStyle *style, HWND hwnd)
+{
+ if (NULL == self)
+ return NULL;
+
+ if (NULL == self->arrowsBitmap &&
+ NULL != style)
+ {
+ self->arrowsBitmap = Image_LoadSkinned(MAKEINTRESOURCE(IDR_CATEGORY_ARROWS_IMAGE),
+ SRC_TYPE_PNG,
+ IMAGE_FILTER_NORMAL,
+ 0, 0,
+ WIDGETSTYLE_CATEGORY_BACK_COLOR(style),
+ WIDGETSTYLE_CATEGORY_TEXT_COLOR(style),
+ WIDGETSTYLE_CATEGORY_BACK_COLOR(style));
+
+ if (NULL != self->arrowsBitmap)
+ Image_Premultiply(self->arrowsBitmap, NULL);
+ }
+
+ return self->arrowsBitmap;
+
+}
+
+HBITMAP
+ListWidget_GetUnknownCommandLargeBitmap(ListWidget *self, WidgetStyle *style, long width, long height)
+{
+ if (NULL == self)
+ return NULL;
+
+ if (NULL == self->unknownCommandLargeImage &&
+ NULL != style)
+ {
+ self->unknownCommandLargeImage = DeviceImageCache_GetImage(Plugin_GetImageCache(),
+ MAKEINTRESOURCE(IDR_UNKNOWN_COMMAND_LARGE_IMAGE),
+ width, height,
+ NULL, NULL);
+ }
+
+ return DeviceImage_GetBitmap(self->unknownCommandLargeImage, DeviceImage_Normal);
+}
+
+HBITMAP
+ListWidget_GetUnknownCommandSmallBitmap(ListWidget *self, WidgetStyle *style, long width, long height)
+{
+ if (NULL == self)
+ return NULL;
+
+ if (NULL == self->unknownCommandSmallImage &&
+ NULL != style)
+ {
+ self->unknownCommandSmallImage = DeviceImageCache_GetImage(Plugin_GetImageCache(),
+ MAKEINTRESOURCE(IDR_UNKNOWN_COMMAND_LARGE_IMAGE),
+ width, height,
+ NULL, NULL);
+ }
+
+ return DeviceImage_GetBitmap(self->unknownCommandSmallImage, DeviceImage_Normal);
+}
+
+HBITMAP
+ListWidget_GetActivityProgressBitmap(ListWidget *self, WidgetStyle *style)
+{
+ if (NULL == self)
+ return NULL;
+
+ if (NULL == self->activityProgressImage &&
+ NULL != style)
+ {
+ self->activityProgressImage = DeviceImageCache_GetImage(Plugin_GetImageCache(),
+ MAKEINTRESOURCE(IDR_PROGRESS_SMALL_IMAGE),
+ self->activityMetrics.progressWidth, self->activityMetrics.progressHeight * LISTWIDGET_PROGRESS_FRAME_COUNT,
+ NULL, NULL);
+
+ }
+
+ return DeviceImage_GetBitmap(self->activityProgressImage, DeviceImage_Normal);
+}
+
+HBITMAP
+ListWidget_GetActivityBadgeBitmap(ListWidget *self, WidgetStyle *style, HWND hwnd, long width, long height)
+{
+ if (NULL == self)
+ return NULL;
+
+ self->activityBadgeBitmap = ListWidget_GetBorderBitmap(self->activityBadgeBitmap,
+ MAKEINTRESOURCE(IDR_ACTION_BACKGROUND_IMAGE),
+ style, hwnd, width, height, TRUE,
+ WIDGETSTYLE_IMAGE_BACK_COLOR(style),
+ WIDGETSTYLE_IMAGE_FRONT_COLOR(style));
+
+ return self->activityBadgeBitmap;
+}
+
+
+static BOOL
+ListWidget_UpdateCommandsLayout(ListWidget *self, HWND hwnd)
+{
+ WidgetStyle *style;
+ ListWidgetItemMetric metrics;
+ RECT rect;
+ size_t index, indexMax;
+ long spacing;
+
+ if (NULL == self)
+ return FALSE;
+
+ style = WIDGET_GET_STYLE(hwnd);
+ if (NULL == style)
+ return FALSE;
+
+ if (0 == self->commandsCount)
+ return TRUE;
+
+ if (FALSE == ListWidget_GetItemMetrics(style, &metrics))
+ return FALSE;
+
+ indexMax = self->commandsCount;
+
+
+ if ((NULL == self->hoveredItem || NULL == self->hoveredItem->activity) &&
+ FALSE != ListWidget_GetCommandPrimary(self->commands[0]))
+ {
+ rect.bottom = self->imageSize.cy /*+ metrics.imageOffsetBottom*/;
+ rect.bottom += metrics.offsetTop + metrics.imageOffsetTop;
+ rect.top = rect.bottom - self->primaryCommandSize.cy;
+
+ rect.left = (self->itemWidth - self->primaryCommandSize.cx)/2;
+ rect.right = rect.left + self->primaryCommandSize.cx;
+
+ ListWidget_SetCommandRect(self->commands[0], &rect);
+ indexMax--;
+ }
+
+ rect.top = metrics.offsetTop + metrics.imageOffsetTop;
+ rect.bottom = rect.top + self->secondaryCommandSize.cy;
+ rect.right = self->itemWidth - metrics.offsetRight - metrics.imageOffsetRight;
+ rect.left = rect.right - self->secondaryCommandSize.cx;
+
+ spacing = self->secondaryCommandSize.cx/16 + 1;
+ for(index = 0; index < indexMax; index++)
+ {
+ ListWidget_SetCommandRect(self->commands[self->commandsCount - index - 1], &rect);
+ OffsetRect(&rect, -(self->secondaryCommandSize.cx + spacing), 0);
+ }
+
+ return TRUE;
+}
+
+
+static BOOL
+ListWidget_UpdateActiveCommands(ListWidget *self, HWND hwnd)
+{
+ ListWidgetCommand **clone;
+ size_t cloneSize, index;
+ BOOL invalidated;
+ POINT origin, pt;
+ RECT rect;
+
+ if (NULL == self)
+ return FALSE;
+
+ if (FALSE == ListWidget_GetViewOrigin(hwnd, &origin))
+ {
+ origin.x = 0;
+ origin.y = 0;
+ }
+
+ if (NULL == self->hoveredItem)
+ {
+ ListWidget_DestroyAllCommands(self->commands, self->commandsCount);
+ self->commandsCount = 0;
+ return TRUE;
+ }
+
+ origin.x += self->hoveredItem->rect.left;
+ origin.y += self->hoveredItem->rect.top;
+ invalidated = FALSE;
+
+ if (0 != self->commandsCount)
+ {
+ for(index = 0; index < self->commandsCount; index++)
+ {
+ if (FALSE != ListWidget_GetCommandRect(self->commands[index], &rect))
+ {
+ OffsetRect(&rect, origin.x, origin.y);
+ if (FALSE != InvalidateRect(hwnd, &rect, FALSE))
+ invalidated = TRUE;
+ }
+ }
+
+ clone = (ListWidgetCommand**)malloc(sizeof(ListWidgetCommand*) * self->commandsCount);
+ if (NULL != clone)
+ {
+ cloneSize = self->commandsCount;
+ CopyMemory(clone, self->commands, sizeof(ListWidgetCommand*) * cloneSize);
+ }
+ else
+ {
+ cloneSize = 0;
+ ListWidget_DestroyAllCommands(self->commands, self->commandsCount);
+ }
+ }
+ else
+ {
+ clone = NULL;
+ cloneSize = 0;
+ }
+
+ self->commandsCount = ListWidget_GetItemCommands(self->hoveredItem,
+ self->commands, self->commandsMax);
+
+ if (0 != self->commandsCount)
+ {
+ ListWidget_UpdateCommandsLayout(self, hwnd);
+ ListWidgetItem_SetInteractive(self->hoveredItem);
+
+ for(index = 0; index < self->commandsCount; index++)
+ {
+ if (FALSE != ListWidget_GetCommandRect(self->commands[index], &rect))
+ {
+ OffsetRect(&rect, origin.x, origin.y);
+ if (FALSE != InvalidateRect(hwnd, &rect, FALSE))
+ invalidated = TRUE;
+ }
+ }
+ }
+ else
+ ListWidgetItem_UnsetInteractive(self->hoveredItem);
+
+
+ if (NULL != clone)
+ {
+ ListWidget_DestroyAllCommands(clone, cloneSize);
+ free(clone);
+ }
+
+ if (FALSE != GetCursorPos(&pt))
+ {
+ ListWidgetItem *tooltipItem;
+ ListWidgetItemPart tooltipItemPart;
+ RECT tooltipItemPartRect;
+
+ tooltipItem = ListWidget_TooltipGetCurrent(self->tooltip, &tooltipItemPart, &tooltipItemPartRect);
+
+ MapWindowPoints(HWND_DESKTOP, hwnd, &pt, 1);
+ ListWidget_UpdateHoverEx(self, hwnd, &pt);
+
+ if (FALSE != ListWidget_TooltipGetChanged(self->tooltip, tooltipItem,
+ tooltipItemPart, &tooltipItemPartRect))
+ {
+ ListWidget_TooltipRelayMouseMessage(self->tooltip, WM_MOUSEMOVE, 0, &pt);
+ }
+ }
+
+ if (FALSE != invalidated)
+ UpdateWindow(hwnd);
+
+ return TRUE;
+}
+
+static void CALLBACK
+ListWidget_ShowCommandTimerCb(HWND hwnd, UINT uMsg, UINT_PTR eventId, DWORD elpasedTime)
+{
+ ListWidget *self;
+
+ KillTimer(hwnd, eventId);
+
+ self = WIDGET_GET_SELF(hwnd, ListWidget);
+ if (NULL == self)
+ return;
+
+ if (NULL != self->hoveredItem &&
+ FALSE == ListWidgetItem_IsInteractive(self->hoveredItem) &&
+ NULL == self->activeMenu)
+ {
+ ListWidget_UpdateActiveCommands(self, hwnd);
+ }
+}
+
+static void CALLBACK
+ListWidget_ProgressTickCb(HWND hwnd, UINT uMsg, UINT_PTR eventId, DWORD elpasedTime)
+{
+ ListWidget *self = WIDGET_GET_SELF(hwnd, ListWidget);
+ if (NULL == self)
+ {
+ KillTimer(hwnd, eventId);
+ return;
+ }
+
+ size_t index = self->activeItems.size();
+ if (index > 0)
+ {
+ POINT origin;
+ RECT rect;
+ ListWidgetItemMetric metrics, *metrics_ptr;
+ WidgetStyle *style;
+
+ if (FALSE == ListWidget_GetViewOrigin(hwnd, &origin))
+ {
+ origin.x = 0;
+ origin.y = 0;
+ }
+
+ style = WIDGET_GET_STYLE(hwnd);
+ if (NULL != style && FALSE != ListWidget_GetItemMetrics(style, &metrics))
+ metrics_ptr = &metrics;
+ else
+ metrics_ptr = NULL;
+
+ while(index--)
+ {
+ ListWidgetItem *item = self->activeItems[index];
+ item->activity->step++;
+ if (item->activity->step >= LISTWIDGET_PROGRESS_FRAME_COUNT)
+ item->activity->step = 1;
+
+ if (FALSE != ListWidget_GetItemActivityProgressRect(self, NULL, item, metrics_ptr, &rect))
+ {
+ OffsetRect(&rect, origin.x, origin.y);
+ InvalidateRect(hwnd, &rect, FALSE);
+ }
+ }
+
+ }
+ else
+ {
+ KillTimer(hwnd, eventId);
+ self->activityTimerEnabled = FALSE;
+ }
+}
+
+
+static void CALLBACK
+ListWidget_BeginTitleEditTimerCb(HWND hwnd, UINT uMsg, UINT_PTR eventId, DWORD elpasedTime)
+{
+ ListWidget *self;
+
+ self = WIDGET_GET_SELF(hwnd, ListWidget);
+
+ KillTimer(hwnd, eventId);
+
+ if (NULL != self &&
+ NULL != self->titleEditItem)
+ {
+
+ if (self->titleEditItem == self->selectedItem)
+ {
+ if (NULL != self->titleEditor)
+ {
+ DestroyWindow(self->titleEditor);
+ self->titleEditor = NULL;
+ }
+
+ self->titleEditor = ListWidget_BeginItemTitleEdit(self, hwnd, self->titleEditItem);
+ }
+
+ self->titleEditItem = NULL;
+ }
+}
+
+BOOL
+ListWidget_UpdateHoverEx(ListWidget *self, HWND hwnd, const POINT *cursor)
+{
+ ListWidgetItem *hoveredItem;
+ ListWidgetItemMetric metrics, *metrics_ptr;
+ WidgetStyle *style;
+ ListWidgetItemPart hoveredPart = ListWidgetItemPart_None;
+ RECT rect, hoveredPartRect;
+ POINT pt, origin;
+
+ if (NULL == cursor)
+ return FALSE;
+
+ metrics_ptr = NULL;
+
+ pt = *cursor;
+
+ if (NULL != self->pressedCategory ||
+ NULL != self->activeMenu ||
+ 0 != (0x8000 & GetAsyncKeyState(VK_LBUTTON)) ||
+ 0 != (0x8000 & GetAsyncKeyState(VK_RBUTTON)))
+ {
+ return FALSE;
+ }
+
+ if (FALSE == ListWidget_GetViewOrigin(hwnd, &origin))
+ {
+ origin.x = 0;
+ origin.y = 0;
+ }
+
+ if (FALSE != GetClientRect(hwnd, &rect) &&
+ FALSE != PtInRect(&rect, pt))
+ {
+
+ pt.x -= origin.x;
+ pt.y -= origin.y;
+ hoveredItem = ListWidget_GetItemFromPoint(self, pt);
+ if (NULL != hoveredItem)
+ {
+ if (NULL == metrics_ptr)
+ {
+ style = WIDGET_GET_STYLE(hwnd);
+
+ if (NULL != style &&
+ FALSE != ListWidget_GetItemMetrics(style, &metrics))
+ {
+ metrics_ptr = &metrics;
+ }
+ }
+
+ hoveredPart = ListWidgetItemPart_Frame |
+ ListWidgetItemPart_Command |
+ ListWidgetItemPart_Spacebar |
+ ListWidgetItemPart_Title;
+
+ hoveredPart = ListWidget_GetItemPartFromPoint(self, hoveredItem, metrics_ptr, pt, hoveredPart, &hoveredPartRect);
+ }
+ }
+ else
+ hoveredItem = NULL;
+
+ if (NULL == hoveredItem)
+ hoveredPart= ListWidgetItemPart_None;
+
+ ListWidget_TooltipUpdate(self->tooltip, hoveredItem, hoveredPart, &hoveredPartRect);
+
+ if (ListWidgetItemPart_None == ((ListWidgetItemPart_Frame | ListWidgetItemPart_Command | ListWidgetItemPart_Activity) & hoveredPart))
+ hoveredItem = NULL;
+
+ if (self->hoveredItem == hoveredItem)
+ return FALSE;
+
+ if (NULL == metrics_ptr)
+ {
+ style = WIDGET_GET_STYLE(hwnd);
+
+ if (NULL != style &&
+ FALSE != ListWidget_GetItemMetrics(style, &metrics))
+ {
+ metrics_ptr = &metrics;
+ }
+ }
+
+ if (NULL != self->hoveredItem)
+ {
+ ListWidgetItem_UnsetHovered(self->hoveredItem);
+ ListWidgetItem_UnsetInteractive(self->hoveredItem);
+
+ if (NULL == metrics_ptr ||
+ FALSE == ListWidget_GetItemFrameRect(self, self->hoveredItem, metrics_ptr, &rect))
+ {
+ CopyRect(&rect, &self->hoveredItem->rect);
+ }
+
+ OffsetRect(&rect, origin.x, origin.y);
+ InvalidateRect(hwnd, &rect, FALSE);
+ }
+
+ if (NULL != hoveredItem)
+ {
+ ListWidgetItem_SetHovered(hoveredItem);
+
+ if (NULL == metrics_ptr ||
+ FALSE == ListWidget_GetItemFrameRect(self, hoveredItem, metrics_ptr, &rect))
+ {
+ CopyRect(&rect, &hoveredItem->rect);
+ }
+
+ OffsetRect(&rect, origin.x, origin.y);
+ InvalidateRect(hwnd, &rect, FALSE);
+
+ SetTimer(hwnd, LISTWIDGETTIMER_SHOW_COMMANDS_ID,
+ LISTWIDGETTIMER_SHOW_COMMANDS_DELAY,
+ ListWidget_ShowCommandTimerCb);
+ }
+ else
+ KillTimer(hwnd, LISTWIDGETTIMER_SHOW_COMMANDS_ID);
+
+ self->hoveredItem = hoveredItem;
+
+ return TRUE;
+}
+
+BOOL
+ListWidget_UpdateHover(ListWidget *self, HWND hwnd)
+{
+ POINT pt;
+ if (FALSE == GetCursorPos(&pt))
+ return FALSE;
+
+ MapWindowPoints(HWND_DESKTOP, hwnd, &pt, 1);
+ return ListWidget_UpdateHoverEx(self, hwnd, &pt);
+}
+
+BOOL
+ListWidget_RemoveHover(ListWidget *self, HWND hwnd, BOOL invalidate)
+{
+ if (NULL == self)
+ return FALSE;
+
+ if (NULL == self->hoveredItem)
+ return FALSE;
+
+ ListWidgetItem_UnsetHovered(self->hoveredItem);
+ ListWidgetItem_UnsetInteractive(self->hoveredItem);
+ KillTimer(hwnd, LISTWIDGETTIMER_SHOW_COMMANDS_ID);
+
+ if (FALSE != invalidate)
+ {
+ RECT rect;
+ POINT origin;
+ ListWidgetItemMetric metrics;
+ WidgetStyle *style;
+
+ style = WIDGET_GET_STYLE(hwnd);
+ if (NULL == style ||
+ FALSE == ListWidget_GetItemMetrics(style, &metrics) ||
+ FALSE == ListWidget_GetItemFrameRect(self, self->hoveredItem, &metrics, &rect))
+ {
+ CopyRect(&rect, &self->hoveredItem->rect);
+ }
+
+ if (FALSE != ListWidget_GetViewOrigin(hwnd, &origin))
+ OffsetRect(&rect, origin.x, origin.y);
+
+ InvalidateRect(hwnd, &rect, FALSE);
+ }
+
+ self->hoveredItem = NULL;
+
+ return TRUE;
+}
+
+
+void
+ListWidget_UpdateSelectionStatus(ListWidget *self, HWND hwnd, BOOL ensureVisible)
+{
+ HWND statusWindow;
+
+ if (NULL == self)
+ return;
+
+ statusWindow = GetParent(hwnd);
+ if (NULL == statusWindow)
+ return;
+
+ statusWindow = MANAGERVIEW_GET_STATUS_BAR(statusWindow);
+ if (NULL == statusWindow)
+ return;
+
+ if (NULL != self->selectedItem)
+ {
+ wchar_t buffer[2048] = {0};
+ const wchar_t *statusString;
+
+ if (FALSE == ListWidget_FormatItemStatus(self, self->selectedItem, buffer, ARRAYSIZE(buffer)) ||
+ L'\0' == *buffer)
+ {
+ statusString = NULL;
+ }
+ else
+ statusString = buffer;
+
+ if (STATUS_ERROR == self->selectionStatus)
+ {
+ self->selectionStatus = STATUSBAR_ADD_STATUS(statusWindow, statusString);
+ if (STATUS_ERROR != self->selectionStatus)
+ {
+ if (FALSE == ListWidget_FormatItemSpaceStatus(self, self->selectedItem, buffer, ARRAYSIZE(buffer)) ||
+ L'\0' == *buffer)
+ {
+ statusString = NULL;
+ }
+ else
+ statusString = buffer;
+
+ STATUSBAR_SET_STATUS_RTEXT(statusWindow, self->selectionStatus, statusString);
+ }
+ }
+ else
+ {
+ STATUSBAR_SET_STATUS_TEXT(statusWindow, self->selectionStatus, statusString);
+
+ if (FALSE == ListWidget_FormatItemSpaceStatus(self, self->selectedItem, buffer, ARRAYSIZE(buffer)) ||
+ L'\0' == *buffer)
+ {
+ statusString = NULL;
+ }
+ else
+ statusString = buffer;
+
+ STATUSBAR_SET_STATUS_RTEXT(statusWindow, self->selectionStatus, statusString);
+
+ if (FALSE != ensureVisible)
+ {
+ STATUSBAR_MOVE_STATUS(statusWindow, self->selectionStatus, STATUS_MOVE_TOP);
+ }
+ }
+ }
+ else
+ {
+ if (STATUS_ERROR != self->selectionStatus)
+ {
+ STATUSBAR_REMOVE_STATUS(statusWindow, self->selectionStatus);
+ self->selectionStatus = STATUS_ERROR;
+ }
+ }
+}
+
+void
+ListWidget_UpdateSelectionSpaceStatus(ListWidget *self, HWND hwnd, BOOL ensureVisible)
+{
+ HWND statusWindow;
+
+ if (NULL == self)
+ return;
+
+ statusWindow = GetParent(hwnd);
+ if (NULL == statusWindow)
+ return;
+
+ statusWindow = MANAGERVIEW_GET_STATUS_BAR(statusWindow);
+ if (NULL == statusWindow)
+ return;
+
+ if (NULL != self->selectedItem &&
+ STATUS_ERROR != self->selectionStatus)
+ {
+ wchar_t buffer[2048] = {0};
+ const wchar_t *statusString;
+
+ if (FALSE == ListWidget_FormatItemSpaceStatus(self, self->selectedItem, buffer, ARRAYSIZE(buffer)) ||
+ L'\0' == *buffer)
+ {
+ statusString = NULL;
+ }
+ else
+ statusString = buffer;
+
+ STATUSBAR_SET_STATUS_RTEXT(statusWindow, self->selectionStatus, statusString);
+
+ if (FALSE != ensureVisible)
+ STATUSBAR_MOVE_STATUS(statusWindow, self->selectionStatus, STATUS_MOVE_TOP);
+ }
+}
+
+BOOL
+ListWidget_SelectItem(ListWidget *self, HWND hwnd, ListWidgetItem *item, BOOL ensureVisible)
+{
+ BOOL invalidated;
+
+ if (NULL == self)
+ return FALSE;
+
+ invalidated = FALSE;
+
+ if (self->selectedItem != item)
+ {
+ RECT rect;
+ POINT origin;
+ ListWidgetItemMetric metrics, *metrics_ptr;
+ WidgetStyle *style;
+
+ if (FALSE == ListWidget_GetViewOrigin(hwnd, &origin))
+ {
+ origin.x = 0;
+ origin.y = 0;
+ }
+
+ style = WIDGET_GET_STYLE(hwnd);
+ if (NULL != style &&
+ FALSE != ListWidget_GetItemMetrics(style, &metrics))
+ {
+ metrics_ptr = &metrics;
+ }
+ else
+ metrics_ptr = NULL;
+
+ if (NULL != self->selectedItem)
+ {
+ ListWidgetItem_UnsetSelected(self->selectedItem);
+
+ if (NULL == metrics_ptr ||
+ FALSE == ListWidget_GetItemFrameRect(self, self->selectedItem, metrics_ptr, &rect))
+ {
+ CopyRect(&rect, &self->selectedItem->rect);
+ }
+
+ OffsetRect(&rect, origin.x, origin.y);
+ if (FALSE != InvalidateRect(hwnd, &rect, FALSE))
+ invalidated = TRUE;
+ }
+
+ if (NULL != item)
+ {
+ ListWidgetItem_SetSelected(item);
+
+ if (NULL == metrics_ptr ||
+ FALSE == ListWidget_GetItemFrameRect(self, item, metrics_ptr, &rect))
+ {
+ CopyRect(&rect, &item->rect);
+ }
+
+ OffsetRect(&rect, origin.x, origin.y);
+ if (FALSE != InvalidateRect(hwnd, &rect, FALSE))
+ invalidated = TRUE;
+ }
+ self->selectedItem = item;
+ }
+
+ if (FALSE != ensureVisible)
+ {
+ if (FALSE != ListWidget_EnsureItemVisisble(self, hwnd, item, VISIBLE_NORMAL))
+ invalidated = TRUE;
+ }
+
+ if (FALSE != invalidated)
+ UpdateWindow(hwnd);
+
+ ListWidget_UpdateSelectionStatus(self, hwnd, TRUE);
+
+
+ return TRUE;
+}
+
+static void
+ListWidget_EnsureFocused(ListWidget *self, HWND hwnd, BOOL forceSelect)
+{
+ HWND hDialog, hParent;
+
+ if (NULL == self || NULL == hwnd)
+ return;
+
+ if (GetFocus() == hwnd)
+ return;
+
+ hDialog = NULL;
+ hParent = hwnd;
+
+ while(NULL != (hParent = GetAncestor(hParent, GA_PARENT)))
+ {
+ if (32770 != GetClassLongPtr(hParent, GCW_ATOM) ||
+ 0 == (WS_EX_CONTROLPARENT & GetWindowStyleEx(hParent)))
+ {
+ break;
+ }
+
+ hDialog = hParent;
+ }
+
+ self->flags |= ListWidgetFlag_NoFocusSelect;
+
+ if (NULL != hDialog)
+ SendMessage(hDialog, WM_NEXTDLGCTL, (WPARAM)hwnd, TRUE);
+ else
+ SetFocus(hwnd);
+
+ self->flags &= ~ListWidgetFlag_NoFocusSelect;
+}
+
+static BOOL
+ListWidget_EnsureTopVisible(ListWidget *self, HWND hwnd)
+{
+ POINT pt;
+
+ if (NULL == self || NULL == hwnd)
+ return FALSE;
+
+
+ if (FALSE == ListWidget_GetViewOrigin(hwnd, &pt) ||
+ (0 == pt.x && 0 == pt.y))
+ {
+ return FALSE;
+ }
+
+ if (FALSE == WIDGET_SCROLL(hwnd, pt.x, pt.y, TRUE))
+ return FALSE;
+
+ ListWidget_UpdateHover(self, hwnd);
+
+ return TRUE;
+}
+
+static BOOL
+ListWidget_EnsureBottomVisible(ListWidget *self, HWND hwnd)
+{
+ SCROLLINFO scrollInfo;
+ POINT pt;
+
+ if (NULL == self || NULL == hwnd)
+ return FALSE;
+
+ scrollInfo.cbSize = sizeof(scrollInfo);
+ scrollInfo.fMask = SIF_POS | SIF_RANGE | SIF_PAGE;
+
+ if (FALSE != GetScrollInfo(hwnd, SB_HORZ, &scrollInfo))
+ pt.x = -scrollInfo.nPos;
+ else
+ pt.x = 0;
+
+ if (FALSE != GetScrollInfo(hwnd, SB_VERT, &scrollInfo) && 0 != scrollInfo.nPage)
+ pt.y = (scrollInfo.nMax + 1 - scrollInfo.nPage) - scrollInfo.nPos;
+ else
+ pt.y = 0;
+
+ if (0 == pt.x && 0 == pt.y)
+ return FALSE;
+
+ if (FALSE == WIDGET_SCROLL(hwnd, pt.x, pt.y, TRUE))
+ return FALSE;
+
+ ListWidget_UpdateHover(self, hwnd);
+
+ return TRUE;
+}
+
+BOOL
+ListWidget_UpdateLayout(HWND hwnd, ListWidgetLayoutFlags layoutFlags)
+{
+ BOOL result;
+ unsigned int flags;
+ ListWidget *self;
+ POINT anchorPoint;
+ ListWidgetItem *anchorItem;
+ SCROLLINFO scrollInfo;
+ HWND managerWindow, focusWindow;
+ POINT pt, menuAnchor, hoverAnchor;
+
+ self = WIDGET_GET_SELF(hwnd, ListWidget);
+ if (NULL == self)
+ return FALSE;
+
+ if (NULL == self->activeMenu ||
+ NULL == self->selectedItem ||
+ FALSE == ListWidget_GetViewItemPos(hwnd, self->selectedItem, &menuAnchor))
+ {
+ menuAnchor.x = 0x7FFFFFFF;
+ menuAnchor.y = 0x7FFFFFFF;
+ }
+
+ if (NULL != self->hoveredItem ||
+ FALSE == ListWidget_GetViewItemPos(hwnd, self->hoveredItem, &hoverAnchor))
+ {
+ hoverAnchor.x = 0x7FFFFFFF;
+ hoverAnchor.y = 0x7FFFFFFF;
+ }
+
+ scrollInfo.cbSize = sizeof(scrollInfo);
+ scrollInfo.fMask = SIF_POS;
+
+ anchorItem = NULL;
+
+ managerWindow = GetAncestor(hwnd, GA_PARENT);
+ if (NULL == managerWindow)
+ managerWindow = hwnd;
+
+ focusWindow = GetFocus();
+
+ if (0 != (ListWidgetLayout_KeepStable & layoutFlags) &&
+ (managerWindow == focusWindow || IsChild(managerWindow, focusWindow)))
+ {
+ if (NULL != self->selectedItem)
+ {
+ RECT clientRect;
+
+ GetClientRect(hwnd, &clientRect);
+
+ anchorPoint.x = (FALSE != GetScrollInfo(hwnd, SB_HORZ, &scrollInfo)) ?
+ -scrollInfo.nPos : 0;
+ anchorPoint.y = (FALSE != GetScrollInfo(hwnd, SB_VERT, &scrollInfo)) ?
+ -scrollInfo.nPos : 0;
+ OffsetRect(&clientRect, -anchorPoint.x, -anchorPoint.y);
+
+ if (clientRect.left <= self->selectedItem->rect.left &&
+ clientRect.top <= self->selectedItem->rect.top &&
+ clientRect.right >= self->selectedItem->rect.right &&
+ clientRect.bottom >= self->selectedItem->rect.bottom)
+ {
+
+ anchorItem = self->selectedItem;
+ anchorPoint.x += self->selectedItem->rect.left;
+ anchorPoint.y += self->selectedItem->rect.top;
+ }
+ }
+ }
+
+ flags = SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE |
+ SWP_FRAMECHANGED | SWP_NOREDRAW;
+
+ result = SetWindowPos(hwnd, NULL, 0, 0, 0, 0, flags);
+
+ if (NULL != anchorItem)
+ {
+ long positionY;
+
+ positionY = anchorItem->rect.top;
+
+ if (FALSE != GetScrollInfo(hwnd, SB_VERT, &scrollInfo))
+ positionY -= scrollInfo.nPos;
+
+ if (positionY != anchorPoint.y)
+ WIDGET_SET_SCROLL_POS(hwnd, 0, positionY - anchorPoint.y, FALSE);
+ }
+
+ if (0x7FFFFFFF != menuAnchor.x &&
+ NULL != self->selectedItem &&
+ FALSE != ListWidget_GetViewItemPos(hwnd, self->selectedItem, &pt) &&
+ menuAnchor.x != pt.x && menuAnchor.y != pt.y)
+ {
+ if (NULL != self->activeMenu)
+ EndMenu();
+ }
+
+ if (0x7FFFFFFF != hoverAnchor.x &&
+ NULL != self->hoveredItem &&
+ FALSE != ListWidget_GetViewItemPos(hwnd, self->hoveredItem, &pt) &&
+ hoverAnchor.x != pt.x && hoverAnchor.y != pt.y)
+ {
+ ListWidget_UpdateHover(self, hwnd);
+ }
+
+ if (0 == (ListWidgetLayout_NoRedraw & layoutFlags))
+ {
+ flags = RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_FRAME | RDW_ERASE;
+
+ if (0 != (ListWidgetLayout_UpdateNow & layoutFlags))
+ flags |= (RDW_ERASENOW | RDW_UPDATENOW);
+
+ RedrawWindow(hwnd, NULL, NULL, flags);
+ }
+
+ return result;
+}
+
+
+double
+ListWidget_GetZoomRatio(ListWidget *self)
+{
+ if (NULL == self)
+ return 1.0;
+
+ if (self->imageSize.cy > LISTWIDGET_IMAGE_DEFAULT_HEIGHT)
+ {
+ return (1.0 + (double)(self->imageSize.cy - LISTWIDGET_IMAGE_DEFAULT_HEIGHT)/
+ (LISTWIDGET_IMAGE_MAX_HEIGHT - LISTWIDGET_IMAGE_DEFAULT_HEIGHT));
+ }
+
+ return (double)(self->imageSize.cy - LISTWIDGET_IMAGE_MIN_HEIGHT)/
+ (LISTWIDGET_IMAGE_DEFAULT_HEIGHT - LISTWIDGET_IMAGE_MIN_HEIGHT);
+
+}
+
+
+long
+ListWidget_GetZoomedValue(double zoomRatio, long normal, long min, long max)
+{
+ if (zoomRatio < 1.0)
+ return min + (long)floor((double)(normal - min) * zoomRatio);
+ else if (zoomRatio > 1.0)
+ return normal + (long)ceil((double)(max - normal) * (zoomRatio - 1.0));
+
+ return normal;
+}
+
+static BOOL
+ListWidget_UpdateActivityLayout(ListWidget *self, HWND hwnd, double zoomRatio)
+{
+ LOGFONTW lf;
+ HDC windowDC;
+ long elementHeight, titleMinWidth, workHeight;
+ ListWidgetItem *item;
+ ListWidgetActivityMetric *activityMetrics;
+
+ // always reset items activity size cache
+ size_t index = (self ? self->activeItems.size() : NULL);
+ while(index--)
+ {
+ item = self->activeItems[index];
+ if (NULL != item)
+ SetSizeEmpty(&item->activity->titleSize);
+ }
+
+ if (NULL == self)
+ return FALSE;
+
+
+ activityMetrics = &self->activityMetrics;
+
+ activityMetrics->offsetLeft = self->activityMetrics.height/16;
+ if (activityMetrics->offsetLeft < 1)
+ activityMetrics->offsetLeft = 1;
+ activityMetrics->offsetRight = activityMetrics->offsetLeft;
+
+ activityMetrics->offsetTop = self->activityMetrics.height/9;
+ if (activityMetrics->offsetTop < 3)
+ activityMetrics->offsetTop = 3;
+ activityMetrics->offsetBottom = activityMetrics->offsetTop;
+
+ activityMetrics->spacing = 4 + self->activityMetrics.height/16;
+
+ workHeight = activityMetrics->height - (activityMetrics->offsetTop + activityMetrics->offsetBottom);
+
+ activityMetrics->fontHeight = workHeight/2;
+ if (activityMetrics->fontHeight < 8)
+ activityMetrics->fontHeight = 8;
+
+ if (NULL == self->activityFont ||
+ sizeof(lf) != GetObject(self->activityFont, sizeof(lf), &lf) ||
+ lf.lfHeight != activityMetrics->fontHeight)
+ {
+ if (NULL != self->activityFont)
+ DeleteObject(self->activityFont);
+
+ lf.lfHeight = activityMetrics->fontHeight;
+ lf.lfWidth = 0;
+ lf.lfEscapement = 0;
+ lf.lfOrientation = 0;
+ lf.lfWeight = FW_NORMAL;
+ lf.lfItalic = FALSE;
+ lf.lfUnderline = FALSE;
+ lf.lfStrikeOut = FALSE;
+ lf.lfCharSet = DEFAULT_CHARSET;
+ lf.lfOutPrecision = OUT_TT_PRECIS;
+ lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
+ lf.lfQuality = Graphics_GetSysFontQuality();
+ lf.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
+ StringCchCopy(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName), L"Arial"/*L"Times New Roman"*/);
+
+ self->activityFont = CreateFontIndirect(&lf);
+ }
+
+ activityMetrics->titleHeight = workHeight;
+ if (activityMetrics->titleHeight < 0)
+ activityMetrics->titleHeight = 0;
+
+ activityMetrics->titleWidth = 0;
+ activityMetrics->percentWidth = 0;
+ activityMetrics->percentHeight = 0;
+ titleMinWidth = 0;
+
+ windowDC = GetWindowDC(hwnd);
+ if (NULL != windowDC)
+ {
+ SIZE textSize;
+ HFONT prevFont;
+
+ prevFont = SelectFont(windowDC, self->activityFont);
+
+ if (FALSE == GetTextExtentPoint32(windowDC, L"00%", 3, &textSize))
+ SetSizeEmpty(&textSize);
+
+ if (textSize.cy <= workHeight)
+ {
+ activityMetrics->fontHeight = textSize.cy;
+ activityMetrics->percentHeight = textSize.cy;
+ activityMetrics->percentWidth = textSize.cx;
+ }
+
+ titleMinWidth = Graphics_GetAveStrWidth(windowDC, 6);
+ activityMetrics->spacing = Graphics_GetAveStrWidth(windowDC, 1) - 2;
+ }
+
+ if (NULL != self->activityBadgeBitmap)
+ {
+ DeleteObject(self->activityBadgeBitmap);
+ self->activityBadgeBitmap = NULL;
+ }
+
+ elementHeight = (long)(18.0 * (double)workHeight/28.0);
+
+ if (activityMetrics->progressWidth != elementHeight ||
+ activityMetrics->progressHeight != elementHeight)
+ {
+ activityMetrics->progressWidth = elementHeight;
+ activityMetrics->progressHeight = elementHeight;
+
+ if (NULL != self->activityProgressImage)
+ {
+ DeviceImage_Release(self->activityProgressImage);
+ self->activityProgressImage = NULL;
+ }
+ }
+
+ for(;;)
+ {
+ activityMetrics->titleWidth = activityMetrics->width -
+ (activityMetrics->offsetLeft + activityMetrics->offsetRight);
+
+ if(0 != activityMetrics->progressWidth)
+ activityMetrics->titleWidth -= (activityMetrics->progressWidth + activityMetrics->spacing);
+
+ if(0 != activityMetrics->percentWidth)
+ activityMetrics->titleWidth -= (activityMetrics->percentWidth + activityMetrics->spacing);
+
+ if (activityMetrics->titleWidth < titleMinWidth)
+ {
+ if (0 != activityMetrics->percentWidth)
+ {
+ activityMetrics->percentWidth = 0;
+ activityMetrics->percentHeight = 0;
+ continue;
+ }
+
+ if (0 != activityMetrics->progressWidth)
+ {
+ activityMetrics->progressWidth = 0;
+ activityMetrics->progressHeight = 0;
+ continue;
+ }
+
+ activityMetrics->titleWidth = 0;
+ activityMetrics->titleHeight = 0;
+ }
+
+ break;
+ }
+
+ return TRUE;
+}
+
+BOOL
+ListWidget_SetImageSize(ListWidget *self, HWND hwnd, int imageWidth, int imageHeight, BOOL redraw)
+{
+ size_t iCategory, iGroup, iItem;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+ long elementWidth, elementHeight;
+ double zoomRatio;
+
+ if (NULL == self)
+ return FALSE;
+
+ SetSize(&self->imageSize, imageWidth, imageHeight);
+
+ zoomRatio = ListWidget_GetZoomRatio(self);
+
+ for (iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ category = self->categories[iCategory];
+ for(iGroup = 0; iGroup < category->groups.size(); iGroup++)
+ {
+ group = category->groups[iGroup];
+ for(iItem = 0; iItem < group->items.size(); iItem++)
+ {
+ item = group->items[iItem];
+ if (NULL != item->image)
+ {
+ DeviceImage_Release(item->image);
+ item->image = NULL;
+ }
+ SetSize(&item->titleSize, -1, -1);
+ }
+ }
+ }
+
+ // connetion size
+ elementHeight = ListWidget_GetZoomedValue(zoomRatio, LISTWIDGET_CONNECTION_DEFAULT_HEIGHT,
+ LISTWIDGET_CONNECTION_MIN_HEIGHT, LISTWIDGET_CONNECTION_MAX_HEIGHT);
+
+ elementWidth = elementHeight;
+ if (self->connectionSize.cx != elementWidth ||
+ self->connectionSize.cy != elementHeight)
+ {
+ SetSize(&self->connectionSize, elementWidth, elementHeight);
+ int index = (int)self->connections.size();
+ while (index--)
+ {
+ ListWidget_UpdateConnectionImageSize(self->connections[index],
+ self->connectionSize.cx, self->connectionSize.cy);
+ }
+ }
+
+
+ // primary command
+ elementHeight = ListWidget_GetZoomedValue(zoomRatio, LISTWIDGET_PRIMARYCOMMAND_DEFAULT_HEIGHT,
+ LISTWIDGET_PRIMARYCOMMAND_MIN_HEIGHT, LISTWIDGET_PRIMARYCOMMAND_MAX_HEIGHT);
+ elementWidth = self->imageSize.cx;
+
+ if (self->primaryCommandSize.cx != elementWidth ||
+ self->primaryCommandSize.cy != elementHeight)
+ {
+ SetSize(&self->primaryCommandSize, elementWidth, elementHeight);
+ }
+
+ // secondary command
+ elementHeight = ListWidget_GetZoomedValue(zoomRatio, LISTWIDGET_SECONDARYCOMMAND_DEFAULT_HEIGHT,
+ LISTWIDGET_SECONDARYCOMMAND_MIN_HEIGHT, LISTWIDGET_SECONDARYCOMMAND_MAX_HEIGHT);
+ elementWidth = elementHeight;
+ if (self->secondaryCommandSize.cx != elementWidth ||
+ self->secondaryCommandSize.cy != elementHeight)
+ {
+ SetSize(&self->secondaryCommandSize, elementWidth, elementHeight);
+ }
+
+ // activity
+ elementHeight = ListWidget_GetZoomedValue(zoomRatio, LISTWIDGET_ACTIVITY_DEFAULT_HEIGHT,
+ LISTWIDGET_ACTIVITY_MIN_HEIGHT, LISTWIDGET_ACTIVITY_MAX_HEIGHT);
+ elementWidth = self->imageSize.cx;
+
+ if (self->activityMetrics.width != elementWidth ||
+ self->activityMetrics.height != elementHeight)
+ {
+ self->activityMetrics.width = elementWidth;
+ self->activityMetrics.height = elementHeight;
+ }
+
+ ListWidget_UpdateLayout(hwnd, ListWidgetLayout_NoRedraw);
+
+ if (NULL != self->hoveredItem &&
+ FALSE != ListWidgetItem_IsInteractive(self->hoveredItem))
+ {
+ ListWidget_UpdateActiveCommands(self, hwnd);
+ }
+
+ ListWidget_UpdateActivityLayout(self, hwnd, zoomRatio);
+
+ if (FALSE != redraw)
+ {
+ RedrawWindow(hwnd, NULL, NULL,
+ RDW_INVALIDATE | RDW_ERASE | RDW_ERASENOW | RDW_UPDATENOW | RDW_ALLCHILDREN);
+ }
+
+ return TRUE;
+}
+
+BOOL
+ListWidget_DisplayContextMenu(ListWidget *self, HWND hostWindow, POINT pt)
+{
+ return FALSE;
+}
+
+BOOL
+ListWidget_RegisterActiveItem(ListWidget *self, HWND hwnd, ListWidgetItem *item)
+{
+ size_t index;
+ if (NULL == self || NULL == item)
+ return FALSE;
+
+ index = self->activeItems.size();
+ while(index--)
+ {
+ if (item == self->activeItems[index])
+ return FALSE;
+ }
+
+ self->activeItems.push_back(item);
+ if (1 == self->activeItems.size())
+ {
+ if (0 != SetTimer(hwnd, LISTWIDGETTIMER_PROGRESS_TICK_ID,
+ LISTWIDGETTIMER_PROGRESS_TICK_DELAY, ListWidget_ProgressTickCb))
+ {
+ self->activityTimerEnabled = TRUE;
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL
+ListWidget_UnregisterActiveItem(ListWidget *self, HWND hwnd, ListWidgetItem *item)
+{
+ size_t index;
+
+ if (NULL == self || NULL == item)
+ return FALSE;
+
+ index = self->activeItems.size();
+ while(index--)
+ {
+ if (item == self->activeItems[index])
+ {
+ self->activeItems.erase(self->activeItems.begin() + index);
+ if (0 == self->activeItems.size())
+ {
+ KillTimer(hwnd, LISTWIDGETTIMER_PROGRESS_TICK_ID);
+ self->activityTimerEnabled = FALSE;
+ }
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+ListWidget_CallItemAction(ListWidget *self, HWND hwnd, ListWidgetCategory *category, ListWidgetItem *item)
+{
+ ifc_device *device;
+
+ if (NULL == self || NULL == item)
+ return;
+
+ if (NULL == category)
+ {
+ if (FALSE == ListWidget_GetItemOwner(self, item, &category) ||
+ NULL == category)
+ {
+ return;
+ }
+ }
+
+ if (NULL == WASABI_API_DEVICES ||
+ S_OK != WASABI_API_DEVICES->DeviceFind(item->name, &device))
+ {
+ return;
+ }
+
+ if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, 0, category->name, -1, "discovered", -1))
+ {
+ if (FALSE == device->GetAttached())
+ device->Attach(NULL);
+ }
+
+ if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, 0, category->name, -1, "attached", -1))
+ {
+ Navigation_SelectDevice(device->GetName());
+ }
+
+ device->Release();
+}
+static BOOL
+ListWidget_AddDevicesToCategory(ListWidget *self, ListWidgetCategory *category, DeviceList *list)
+{
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+ ifc_device *device;
+ size_t index, groupCount, categoryCount;
+
+ if (NULL == list || NULL == category)
+ return FALSE;
+
+ categoryCount = category->groups.size();
+
+ index = list->size();
+ if (0 == index)
+ return FALSE;
+
+ do
+ {
+ index--;
+ device = list->at(index);
+ const char *groupName = device->GetType();
+ group = ListWidget_FindGroupEx(category, groupName, categoryCount);
+ if (NULL == group)
+ {
+ group = ListWidget_CreateGroup(groupName);
+ if (NULL != group)
+ ListWidget_AddGroup(category, group);
+ }
+
+ if (NULL != group)
+ {
+ groupCount = group->items.size();
+ item = ListWidget_FindGroupItemEx(group, device->GetName(), groupCount);
+ if (NULL == item)
+ {
+ item = ListWidget_CreateItemFromDevice(self, device);
+ if (NULL != item)
+ {
+ ListWidget_AddItem(group, item);
+ if (NULL != item->activity)
+ self->activeItems.push_back(item);
+ }
+ }
+ }
+ else
+ groupCount = 0;
+
+ list->erase(list->begin() + index);
+ device->Release();
+
+ while(index--)
+ {
+ device = list->at(index);
+ if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, 0, groupName, -1, device->GetType(), -1))
+ {
+ if (NULL != group)
+ {
+ item = ListWidget_FindGroupItemEx(group, device->GetName(), groupCount);
+ if (NULL == item)
+ {
+ item = ListWidget_CreateItemFromDevice(self, device);
+ if (NULL != item)
+ {
+ ListWidget_AddItem(group, item);
+ if (NULL != item->activity)
+ {
+ self->activeItems.push_back(item);
+ }
+ }
+ }
+ }
+ list->erase(list->begin() + index);
+ device->Release();
+ }
+ }
+
+ if (NULL != group)
+ ListWidget_SortGroup(group);
+
+ index = list->size();
+
+ } while(0 != index);
+
+ ListWidget_SortCategory(category);
+
+ return TRUE;
+}
+
+static void
+ListWidget_AddExistingDevices(ListWidget *self, HWND hwnd)
+{
+ DeviceList discoveredList, attachedList;
+ ifc_device *device;
+ ListWidgetCategory *category;
+ size_t index, activeItemsCount;
+ BOOL devicesAdded;
+
+ if (NULL == self)
+ return;
+
+ activeItemsCount = self->activeItems.size();
+
+ if (NULL != WASABI_API_DEVICES)
+ {
+ ifc_deviceobjectenum *enumerator;
+ if (SUCCEEDED(WASABI_API_DEVICES->DeviceEnumerate(&enumerator)))
+ {
+ size_t reserveSize;
+ ifc_deviceobject *object;
+
+ if (SUCCEEDED(enumerator->GetCount(&reserveSize)))
+ {
+ discoveredList.reserve(reserveSize);
+ attachedList.reserve(reserveSize);
+ }
+
+ while(S_OK == enumerator->Next(&object, 1, NULL))
+ {
+ if (SUCCEEDED(object->QueryInterface(IFC_Device, (void**)&device)))
+ {
+ if (FALSE == device->GetHidden() &&
+ // excludes 'cloud' devices from appearing
+ lstrcmpiA(device->GetConnection(), "cloud"))
+ {
+ if (FALSE == device->GetAttached())
+ discoveredList.push_back(device);
+ else
+ attachedList.push_back(device);
+ }
+ else
+ device->Release();
+ }
+ object->Release();
+ }
+ enumerator->Release();
+ }
+ }
+
+ devicesAdded = FALSE;
+ if (0 != attachedList.size())
+ {
+ category = ListWidget_FindCategory(self, "attached");
+ if (NULL != category)
+ {
+ if (FALSE != ListWidget_AddDevicesToCategory(self, category, &attachedList))
+ {
+ ListWidget_ResetCategoryCounter(category);
+ devicesAdded = TRUE;
+ }
+ }
+ index = attachedList.size();
+ while(index--)
+ attachedList[index]->Release();
+ }
+
+ if (0 != discoveredList.size())
+ {
+ category = ListWidget_FindCategory(self, "discovered");
+ if (NULL != category)
+ {
+ if (FALSE != ListWidget_AddDevicesToCategory(self, category, &discoveredList))
+ {
+ ListWidget_ResetCategoryCounter(category);
+ devicesAdded = TRUE;
+ }
+ }
+ index = discoveredList.size();
+ while(index--)
+ discoveredList[index]->Release();
+ }
+
+ if (self->activeItems.size() > 0)
+ {
+ if (0 == activeItemsCount)
+ {
+ if (0 != SetTimer(hwnd, LISTWIDGETTIMER_PROGRESS_TICK_ID,
+ LISTWIDGETTIMER_PROGRESS_TICK_DELAY, ListWidget_ProgressTickCb))
+ {
+ self->activityTimerEnabled = TRUE;
+ }
+ }
+ }
+ else
+ {
+ if (0 != activeItemsCount)
+ {
+ KillTimer(hwnd, LISTWIDGETTIMER_PROGRESS_TICK_ID);
+ self->activityTimerEnabled = FALSE;
+ }
+ }
+
+ if (FALSE != devicesAdded)
+ {
+ ListWidget_UpdateLayout(hwnd, ListWidgetLayout_Normal);
+ }
+}
+
+
+void
+ListWidget_UpdateTitleEditorColors(HWND editor, WidgetStyle *style)
+{
+ if (NULL == editor || NULL == style)
+ return;
+
+ EMBEDDEDEDITOR_SET_TEXT_COLOR(editor, WIDGETSTYLE_TEXT_COLOR(style));
+ EMBEDDEDEDITOR_SET_BACK_COLOR(editor, WIDGETSTYLE_BACK_COLOR(style));
+ EMBEDDEDEDITOR_SET_BORDER_COLOR(editor, WIDGETSTYLE_TEXT_EDITOR_BORDER_COLOR(style));
+
+ InvalidateRect(editor, NULL, TRUE);
+}
+
+static BOOL
+ListWidget_DeviceAdd(HWND hwnd, ifc_device *device, DeviceImage *deviceImage)
+{
+ ListWidget *self;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+
+ self = WIDGET_GET_SELF(hwnd, ListWidget);
+ if (NULL == self)
+ return FALSE;
+
+ if (NULL == self ||
+ NULL == device ||
+ FALSE != device->GetHidden() &&
+ // excludes 'cloud' devices from appearing
+ lstrcmpiA(device->GetConnection(), "cloud"))
+ {
+ return FALSE;
+ }
+
+ category = ListWidget_FindCategory(self, (FALSE != device->GetAttached()) ? "attached" : "discovered");
+ if (NULL == category)
+ return FALSE;
+
+ group = ListWidget_FindGroup(category, device->GetType());
+ if (NULL == group)
+ {
+ group = ListWidget_CreateGroup(device->GetType());
+ if (NULL == group)
+ return FALSE;
+
+ ListWidget_AddGroup(category, group);
+ ListWidget_SortCategory(category);
+ }
+
+ item = ListWidget_FindGroupItem(group, device->GetName());
+ if (NULL != item)
+ return FALSE;
+
+ item = ListWidget_CreateItemFromDevice(self, device);
+ if (NULL == item)
+ return NULL;
+
+ if (NULL == item->image && NULL != deviceImage)
+ {
+ item->image = deviceImage;
+ DeviceImage_AddRef(item->image);
+ }
+
+ ListWidget_AddItem(group, item);
+ ListWidget_SortGroup(group);
+
+ if (NULL != item->activity)
+ ListWidget_RegisterActiveItem(self, hwnd, item);
+
+ ListWidget_ResetCategoryCounter(category);
+
+ if (FALSE == category->collapsed)
+ {
+ ListWidget_UpdateLayout(hwnd,
+ ListWidgetLayout_UpdateNow | ListWidgetLayout_KeepStable);
+ }
+ else
+ {
+ RECT rect;
+ POINT origin;
+
+ CopyRect(&rect, &category->rect);
+ if (FALSE != ListWidget_GetViewOrigin(hwnd, &origin))
+ OffsetRect(&rect, origin.x, origin.y);
+
+ InvalidateRect(hwnd, &rect, FALSE);
+ }
+ return TRUE;
+}
+
+static BOOL
+ListWidget_DeviceRemove(HWND hwnd, ifc_device *device)
+{
+ ListWidget *self;
+ self = WIDGET_GET_SELF(hwnd, ListWidget);
+
+ if (NULL == self || NULL == device)
+ return FALSE;
+
+ ListWidget_RemoveItem(self, hwnd, device->GetName());
+ ListWidget_UpdateHover(self, hwnd);
+
+ return TRUE;
+}
+
+
+static BOOL
+ListWidget_DeviceAttach(HWND hwnd, ifc_device *device)
+{
+ ListWidget *self;
+ ListWidgetCategory *category;
+ ListWidgetItem *item;
+ DeviceImage *image;
+ BOOL result;
+
+ self = WIDGET_GET_SELF(hwnd, ListWidget);
+
+ if (NULL == self || NULL == device)
+ return FALSE;
+
+ image = NULL;
+
+ category = ListWidget_FindCategory(self, "discovered");
+ if (NULL != category)
+ {
+ ListWidgetGroup *group = ListWidget_FindGroup(category, device->GetType());
+ if (NULL != group)
+ {
+ item = ListWidget_FindGroupItem(group, device->GetName());
+ if (NULL != item)
+ {
+ image = item->image;
+ if (NULL != image)
+ DeviceImage_AddRef(image);
+ ListWidget_RemoveItem(self, hwnd, device->GetName());
+ }
+ }
+ }
+
+ result = ListWidget_DeviceAdd(hwnd, device, image);
+
+ if (NULL != image)
+ DeviceImage_Release(image);
+
+ return result;
+}
+
+static BOOL
+ListWidget_DeviceDetach(HWND hwnd, ifc_device *device)
+{
+ ListWidget *self;
+ ListWidgetCategory *category;
+ ListWidgetItem *item;
+ DeviceImage *image;
+ BOOL result;
+
+ self = WIDGET_GET_SELF(hwnd, ListWidget);
+
+ if (NULL == self || NULL == device)
+ return FALSE;
+
+ image = NULL;
+
+ category = ListWidget_FindCategory(self, "attached");
+ if (NULL != category)
+ {
+ ListWidgetGroup *group = ListWidget_FindGroup(category, device->GetType());
+ if (NULL != group)
+ {
+ item = ListWidget_FindGroupItem(group, device->GetName());
+ if (NULL != item)
+ {
+ image = item->image;
+ if (NULL != image)
+ DeviceImage_AddRef(image);
+ ListWidget_RemoveItem(self, hwnd, device->GetName());
+ }
+ }
+ }
+
+ result = ListWidget_DeviceAdd(hwnd, device, image);
+
+ if (NULL != image)
+ DeviceImage_Release(image);
+
+ return result;
+}
+
+static BOOL
+ListWidget_DeviceTitleChanged(HWND hwnd, ifc_device *device)
+{
+ ListWidget *self;
+ ListWidgetItem *item;
+ ListWidgetCategory *category;
+ wchar_t buffer[1024] = {0};
+
+ self = WIDGET_GET_SELF(hwnd, ListWidget);
+
+ if (NULL == self || NULL == device)
+ return FALSE;
+
+ item = ListWidget_FindItem(self, device->GetName(), &category, NULL);
+ if (NULL == item)
+ return FALSE;
+
+ if (FAILED(device->GetDisplayName(buffer, ARRAYSIZE(buffer))))
+ return FALSE;
+
+ ListWidget_SetItemTitle(item, buffer);
+
+ if (NULL == category ||
+ FALSE == category->collapsed)
+ {
+ ListWidget_UpdateLayout(hwnd, ListWidgetLayout_UpdateNow | ListWidgetLayout_KeepStable);
+ ListWidget_TooltipUpdateText(self, self->tooltip, item, Tooltip_DeviceTitleChanged);
+
+ if (STATUS_ERROR != self->selectionStatus &&
+ item == self->selectedItem)
+ {
+ ListWidget_UpdateSelectionStatus(self, hwnd, FALSE);
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL
+ListWidget_DeviceSpaceChanged(HWND hwnd, ifc_device *device)
+{
+ ListWidget *self;
+ ListWidgetItem *item;
+ ListWidgetCategory *category;
+
+ self = WIDGET_GET_SELF(hwnd, ListWidget);
+
+ if (NULL == self || NULL == device)
+ return FALSE;
+
+ item = ListWidget_FindItem(self, device->GetName(), &category, NULL);
+ if (NULL == item)
+ return FALSE;
+
+ if (FAILED(device->GetTotalSpace(&item->spaceTotal)))
+ return FALSE;
+
+ if (FAILED(device->GetUsedSpace(&item->spaceUsed)))
+ return FALSE;
+
+ if (NULL == category ||
+ FALSE == category->collapsed)
+ {
+ RECT rect;
+ POINT origin;
+ ListWidgetItemMetric metrics;
+ WidgetStyle *style;
+
+ CopyRect(&rect, &item->rect);
+ if (FALSE != ListWidget_GetViewOrigin(hwnd, &origin))
+ OffsetRect(&rect, origin.x, origin.y);
+
+ style = WIDGET_GET_STYLE(hwnd);
+ if (NULL != style && FALSE != ListWidget_GetItemMetrics(style, &metrics))
+ {
+ rect.top += metrics.offsetTop +
+ metrics.imageOffsetTop +
+ self->imageSize.cy +
+ metrics.imageOffsetBottom +
+ metrics.spacebarOffsetTop;
+ rect.left += metrics.offsetLeft;
+ rect.right -= metrics.offsetRight;
+ rect.bottom = rect.top + metrics.spacebarHeight;
+ }
+
+ InvalidateRect(hwnd, &rect, FALSE);
+ UpdateWindow(hwnd);
+
+ ListWidget_TooltipUpdateText(self, self->tooltip, item, Tooltip_DeviceSpaceChanged);
+
+ if (STATUS_ERROR != self->selectionStatus &&
+ item == self->selectedItem)
+ {
+ ListWidget_UpdateSelectionSpaceStatus(self, hwnd, FALSE);
+ }
+ }
+
+
+ return TRUE;
+}
+
+static BOOL
+ListWidget_DeviceIconChanged(HWND hwnd, ifc_device *device)
+{
+ ListWidget *self;
+ ListWidgetItem *item;
+ ListWidgetCategory *category;
+ DeviceImage *previousImage;
+
+ self = WIDGET_GET_SELF(hwnd, ListWidget);
+
+ if (NULL == self || NULL == device)
+ return FALSE;
+
+ item = ListWidget_FindItem(self, device->GetName(), &category, NULL);
+ if (NULL == item)
+ return FALSE;
+
+ previousImage = item->image;
+ item->image = NULL;
+
+ if (NULL == category ||
+ FALSE == category->collapsed)
+ {
+ RECT rect;
+ POINT origin;
+ ListWidgetItemMetric metrics;
+ WidgetStyle *style;
+
+
+ style = WIDGET_GET_STYLE(hwnd);
+ if (NULL == style ||
+ FALSE == ListWidget_GetItemMetrics(style, &metrics) ||
+ FALSE == ListWidget_GetItemImageRect(self, item, &metrics, &rect))
+ {
+ CopyRect(&rect, &item->rect);
+ }
+
+ if (FALSE != ListWidget_GetViewOrigin(hwnd, &origin))
+ OffsetRect(&rect, origin.x, origin.y);
+
+ InvalidateRect(hwnd, &rect, FALSE);
+ UpdateWindow(hwnd);
+ }
+
+ if (NULL != previousImage)
+ DeviceImage_Release(previousImage);
+
+ return TRUE;
+}
+
+static BOOL
+ListWidget_DeviceCommandChanged(HWND hwnd, ifc_device *device)
+{
+ ListWidget *self;
+ ListWidgetItem *item;
+
+
+ self = WIDGET_GET_SELF(hwnd, ListWidget);
+
+ if (NULL == self || NULL == device)
+ return FALSE;
+
+ item = ListWidget_FindItem(self, device->GetName(), NULL, NULL);
+ if (NULL == item)
+ return FALSE;
+
+ if (item == self->hoveredItem &&
+ NULL == self->activeMenu)
+ {
+ ListWidget_UpdateActiveCommands(self, hwnd);
+ }
+
+ return TRUE;
+}
+
+static BOOL
+ListWidget_DeviceActivityStarted(HWND hwnd, ifc_device *device)
+{
+ ListWidget *self;
+ ListWidgetItem *item;
+ ListWidgetCategory *category;
+ size_t index;
+
+ self = WIDGET_GET_SELF(hwnd, ListWidget);
+
+ if (NULL == self || NULL == device)
+ return FALSE;
+
+ item = ListWidget_FindItem(self, device->GetName(), &category, NULL);
+ if (NULL == item)
+ return FALSE;
+
+
+ index = self->activeItems.size();
+ while(index--)
+ {
+ if (item == self->activeItems[index])
+ return FALSE;
+ }
+
+ if (FALSE != ListWidget_CreateItemActivity(item))
+ {
+ ifc_deviceactivity *activity;
+
+ ListWidget_RegisterActiveItem(self, hwnd, item);
+
+ if(S_OK == device->GetActivity(&activity) &&
+ NULL != activity)
+ {
+ ListWidget_UpdateItemActivity(item, activity);
+ activity->Release();
+ }
+ }
+
+
+ if (NULL == category ||
+ FALSE == category->collapsed)
+ {
+ ListWidget_InvalidateItemImage(self, hwnd, item);
+
+ if (STATUS_ERROR != self->selectionStatus &&
+ item == self->selectedItem)
+ {
+ ListWidget_UpdateSelectionStatus(self, hwnd, FALSE);
+ }
+
+ }
+
+ if (item == self->hoveredItem &&
+ NULL == self->activeMenu)
+ {
+ ListWidget_UpdateActiveCommands(self, hwnd);
+ }
+
+ return TRUE;
+}
+
+static BOOL
+ListWidget_DeviceActivityFinished(HWND hwnd, ifc_device *device)
+{
+ ListWidget *self;
+ ListWidgetItem *item;
+ ListWidgetCategory *category;
+ BOOL activityChanged;
+
+ self = WIDGET_GET_SELF(hwnd, ListWidget);
+
+ if (NULL == self || NULL == device)
+ return FALSE;
+
+ item = ListWidget_FindItem(self, device->GetName(), &category, NULL);
+ if (NULL == item)
+ return FALSE;
+
+ activityChanged = FALSE;
+
+ if (FALSE != ListWidget_UnregisterActiveItem(self, hwnd, item))
+ activityChanged = TRUE;
+ if (FALSE != ListWidget_DeleteItemActivity(item))
+ activityChanged = TRUE;
+
+ if (FALSE != activityChanged)
+ {
+ if (NULL == category ||
+ FALSE == category->collapsed)
+ {
+ ListWidget_InvalidateItemImage(self, hwnd, item);
+ }
+
+ if (item == self->hoveredItem &&
+ NULL == self->activeMenu)
+ {
+ ListWidget_UpdateActiveCommands(self, hwnd);
+ }
+
+ if (STATUS_ERROR != self->selectionStatus &&
+ item == self->selectedItem)
+ {
+ ListWidget_UpdateSelectionStatus(self, hwnd, FALSE);
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL
+ListWidget_DeviceActivityChanged(HWND hwnd, ifc_device *device)
+{
+ ListWidget *self;
+ ListWidgetItem *item;
+ ListWidgetCategory *category;
+ ifc_deviceactivity *activity;
+ ListWidgetActivityChange changes;
+
+ self = WIDGET_GET_SELF(hwnd, ListWidget);
+
+ if (NULL == self || NULL == device)
+ return FALSE;
+
+ item = ListWidget_FindItem(self, device->GetName(), &category, NULL);
+ if (NULL == item)
+ return FALSE;
+
+ if (S_OK == device->GetActivity(&activity) &&
+ NULL != activity)
+ {
+ changes = ListWidget_UpdateItemActivity(item, activity);
+ activity->Release();
+ }
+ else
+ changes = ListWidgetActivityChanged_Nothing;
+
+ //if (FALSE != self->activityTimerEnabled)
+ // changes &= ~ListWidgetActivityChanged_Percent;
+
+ if (ListWidgetActivityChanged_Nothing != changes &&
+ (NULL == category || FALSE == category->collapsed))
+ {
+ ListWidget_InvalidateItemActivity(self, hwnd, item, changes);
+ ListWidget_TooltipUpdateText(self, self->tooltip, item, Tooltip_DeviceActivityChanged);
+
+ if (0 != (ListWidgetActivityChanged_Title & changes) &&
+ STATUS_ERROR != self->selectionStatus &&
+ item == self->selectedItem)
+ {
+ ListWidget_UpdateSelectionStatus(self, hwnd, FALSE);
+ }
+
+ }
+
+
+ return TRUE;
+}
+
+static BOOL
+ListWidget_DeviceModelChanged(HWND hwnd, ifc_device *device)
+{
+ ListWidget *self;
+ ListWidgetItem *item;
+ ListWidgetCategory *category;
+
+ self = WIDGET_GET_SELF(hwnd, ListWidget);
+
+ if (NULL == self || NULL == device)
+ return FALSE;
+
+ item = ListWidget_FindItem(self, device->GetName(), &category, NULL);
+ if (NULL == item)
+ return FALSE;
+
+
+ if (NULL == category ||
+ FALSE == category->collapsed)
+ {
+ ListWidget_TooltipUpdateText(self, self->tooltip, item, Tooltip_DeviceModelChanged);
+
+ if (STATUS_ERROR != self->selectionStatus &&
+ item == self->selectedItem)
+ {
+ ListWidget_UpdateSelectionStatus(self, hwnd, FALSE);
+ }
+ }
+ return TRUE;
+}
+
+static BOOL
+ListWidget_DeviceStatusChanged(HWND hwnd, ifc_device *device)
+{
+ ListWidget *self;
+ ListWidgetItem *item;
+ ListWidgetCategory *category;
+
+ self = WIDGET_GET_SELF(hwnd, ListWidget);
+
+ if (NULL == self || NULL == device)
+ return FALSE;
+
+ item = ListWidget_FindItem(self, device->GetName(), &category, NULL);
+ if (NULL == item)
+ return FALSE;
+
+
+ if (NULL == category ||
+ FALSE == category->collapsed)
+ {
+ ListWidget_TooltipUpdateText(self, self->tooltip, item, Tooltip_DeviceStatusChanged);
+
+ if (STATUS_ERROR != self->selectionStatus &&
+ item == self->selectedItem)
+ {
+ ListWidget_UpdateSelectionStatus(self, hwnd, FALSE);
+ }
+ }
+ return TRUE;
+}
+
+static void
+ListWidget_DeviceCb(ifc_device *device, DeviceEvent eventId, void *user)
+{
+ HWND hwnd;
+ hwnd = (HWND)user;
+
+ switch(eventId)
+ {
+ case Event_DeviceAdded:
+ ListWidget_DeviceAdd(hwnd, device, NULL);
+ break;
+ case Event_DeviceRemoved:
+ ListWidget_DeviceRemove(hwnd, device);
+ break;
+ case Event_DeviceHidden:
+ ListWidget_DeviceRemove(hwnd, device);
+ break;
+ case Event_DeviceShown:
+ ListWidget_DeviceAdd(hwnd, device, NULL);
+ break;
+ case Event_DeviceAttached:
+ ListWidget_DeviceAttach(hwnd, device);
+ break;
+ case Event_DeviceDetached:
+ ListWidget_DeviceDetach(hwnd, device);
+ break;
+ case Event_DeviceDisplayNameChanged:
+ ListWidget_DeviceTitleChanged(hwnd, device);
+ break;
+ case Event_DeviceTotalSpaceChanged:
+ ListWidget_DeviceSpaceChanged(hwnd, device);
+ break;
+ case Event_DeviceUsedSpaceChanged:
+ ListWidget_DeviceSpaceChanged(hwnd, device);
+ break;
+ case Event_DeviceIconChanged:
+ ListWidget_DeviceIconChanged(hwnd, device);
+ break;
+ case Event_DeviceActivityStarted:
+ ListWidget_DeviceActivityStarted(hwnd, device);
+ break;
+ case Event_DeviceActivityFinished:
+ ListWidget_DeviceActivityFinished(hwnd, device);
+ break;
+ case Event_DeviceActivityChanged:
+ ListWidget_DeviceActivityChanged(hwnd, device);
+ break;
+ case Event_DeviceCommandChanged:
+ ListWidget_DeviceCommandChanged(hwnd, device);
+ break;
+ case Event_DeviceModelChanged:
+ ListWidget_DeviceModelChanged(hwnd, device);
+ break;
+ case Event_DeviceStatusChanged:
+ ListWidget_DeviceStatusChanged(hwnd, device);
+ break;
+
+ }
+}
+
+
+static BOOL
+ListWidget_RegisterDeviceHandler(ListWidget *self, HWND hwnd)
+{
+ HWND eventRelay;
+ DeviceEventCallbacks callbacks;
+
+ if (NULL == self)
+ return FALSE;
+
+ if (0 != self->deviceHandler)
+ return FALSE;
+
+ eventRelay = Plugin_GetEventRelayWindow();
+ if (NULL == eventRelay)
+ return FALSE;
+
+ ZeroMemory(&callbacks, sizeof(callbacks));
+ callbacks.deviceCb = ListWidget_DeviceCb;
+
+ self->deviceHandler = EVENTRELAY_REGISTER_HANDLER(eventRelay, &callbacks, hwnd);
+ return (0 != self->deviceHandler);
+}
+
+static BOOL
+ListWidget_InitCb(HWND hwnd, void **object, void *param)
+{
+ ListWidget *self;
+ HWND sliderWindow;
+ int imageHeight, imageWidth;
+
+ self = new ListWidget();
+ if (NULL == self)
+ return FALSE;
+
+ self->flags = (ListWidgetFlags)0;
+ self->hoveredItem = NULL;
+ self->selectedItem = NULL;
+ self->titleEditItem = NULL;
+ self->pressedCategory = NULL;
+ self->itemWidth = 0;
+ self->spacebarBitmap = NULL;
+ self->hoverBitmap = NULL;
+ self->selectBitmap = NULL;
+ self->inactiveSelectBitmap = NULL;
+ self->largeBadgeBitmap = NULL;
+ self->smallBadgeBitmap = NULL;
+ self->arrowsBitmap = NULL;
+ self->itemsPerLine = 0;
+ self->deviceHandler = 0;
+ self->activeMenu = NULL;
+ self->previousMouse.x = -1;
+ self->previousMouse.y = -1;
+ self->commands = NULL;
+ self->commandsCount = 0;
+ self->commandsMax = 0;
+ self->unknownCommandLargeImage = NULL;
+ self->unknownCommandSmallImage = NULL;
+
+ ZeroMemory(&self->activityMetrics, sizeof(ListWidgetActivityMetric));
+ self->activityFont = NULL;
+ self->activityProgressImage = NULL;
+ self->activityBadgeBitmap = NULL;
+ self->activityTimerEnabled = FALSE;
+
+ SetSizeEmpty(&self->connectionSize);
+ SetSizeEmpty(&self->primaryCommandSize);
+ SetSizeEmpty(&self->secondaryCommandSize);
+
+ self->selectionStatus = STATUS_ERROR;
+
+ self->titleEditor = NULL;
+
+ BackBuffer_Initialize(&self->backBuffer, hwnd);
+
+ imageHeight = Config_ReadInt("View", "imageHeight", LISTWIDGET_IMAGE_DEFAULT_HEIGHT);
+ if (imageHeight < LISTWIDGET_IMAGE_MIN_HEIGHT)
+ imageHeight = LISTWIDGET_IMAGE_MIN_HEIGHT;
+ else if (imageHeight > LISTWIDGET_IMAGE_MAX_HEIGHT)
+ imageHeight = LISTWIDGET_IMAGE_MAX_HEIGHT;
+
+ imageWidth = (imageHeight * LISTWIDGET_IMAGE_DEFAULT_WIDTH)/LISTWIDGET_IMAGE_DEFAULT_HEIGHT;
+
+ ListWidget_SetImageSize(self, hwnd, imageWidth, imageHeight, FALSE);
+
+ ListWidget_CreateDefaultCategories(self);
+
+ *object = self;
+
+ sliderWindow = MANAGERVIEW_GET_ZOOM_SLIDER(GetParent(hwnd));
+ if (NULL != sliderWindow)
+ {
+ int pos;
+
+ SendMessage(sliderWindow, TBM_SETPAGESIZE, 0, 10);
+ SendMessage(sliderWindow, TBM_SETLINESIZE, 0, 10);
+ SendMessage(sliderWindow, TBM_SETRANGE, TRUE, MAKELPARAM(-100, 100));
+ SendMessage(sliderWindow, TBM_SETTIC, 0, 0);
+
+ if (imageHeight == LISTWIDGET_IMAGE_DEFAULT_HEIGHT)
+ pos = 0;
+ else if (imageHeight > LISTWIDGET_IMAGE_DEFAULT_HEIGHT)
+ pos = (100 * (imageHeight- LISTWIDGET_IMAGE_DEFAULT_HEIGHT))/(LISTWIDGET_IMAGE_MAX_HEIGHT - LISTWIDGET_IMAGE_DEFAULT_HEIGHT);
+ else
+ pos = (100 * (imageHeight - LISTWIDGET_IMAGE_MIN_HEIGHT))/(LISTWIDGET_IMAGE_DEFAULT_HEIGHT - LISTWIDGET_IMAGE_MIN_HEIGHT) - 100;
+
+ SendMessage(sliderWindow, TBM_SETPOS, TRUE, pos);
+ }
+
+ self->tooltip = ListWidget_TooltipCreate(hwnd);
+
+ ListWidget_AddExistingDevices(self, hwnd);
+ ListWidget_RegisterDeviceHandler(self, hwnd);
+ return TRUE;
+}
+
+static void
+ListWidget_DestroyCb(ListWidget *self, HWND hwnd)
+{
+ HWND sliderWindow;
+ size_t index;
+
+ if (NULL == self)
+ return;
+
+
+ Config_WriteInt("View", "imageHeight", self->imageSize.cy);
+
+ if (0 != self->deviceHandler)
+ {
+ HWND eventRelay;
+ eventRelay = Plugin_GetEventRelayWindow();
+ if (NULL != eventRelay)
+ {
+ EVENTRELAY_UNREGISTER_HANDLER(eventRelay, self->deviceHandler);
+ }
+ }
+
+ index = self->activeItems.size();
+ if (0 != index)
+ {
+ KillTimer(hwnd, LISTWIDGETTIMER_PROGRESS_TICK_ID);
+ self->activityTimerEnabled = FALSE;
+ while(index--)
+ ListWidget_DeleteItemActivity(self->activeItems[index]);
+
+ }
+
+ index = self->categories.size();
+ if (0 != index)
+ {
+ while(index--)
+ {
+ ListWidgetCategory *category = self->categories[index];
+ Config_WriteBool("CollapsedCategories", category->name, category->collapsed);
+ ListWidget_DestroyCategory(category);
+ }
+ self->categories.clear();
+ }
+
+ ListWidget_RemoveAllConnections(self);
+
+ BackBuffer_Uninitialize(&self->backBuffer);
+
+ if (NULL != self->spacebarBitmap)
+ DeleteObject(self->spacebarBitmap);
+
+ if (NULL != self->hoverBitmap)
+ DeleteObject(self->hoverBitmap);
+
+ if (NULL != self->selectBitmap)
+ DeleteObject(self->selectBitmap);
+
+ if (NULL != self->inactiveSelectBitmap)
+ DeleteObject(self->inactiveSelectBitmap);
+
+ if (NULL != self->largeBadgeBitmap)
+ DeleteObject(self->largeBadgeBitmap);
+
+ if (NULL != self->smallBadgeBitmap)
+ DeleteObject(self->smallBadgeBitmap);
+
+ if (NULL != self->unknownCommandLargeImage)
+ DeviceImage_Release(self->unknownCommandLargeImage);
+
+ if (NULL != self->unknownCommandSmallImage)
+ DeviceImage_Release(self->unknownCommandSmallImage);
+
+ if (NULL != self->arrowsBitmap)
+ DeleteObject(self->arrowsBitmap);
+
+ if (NULL != self->activityProgressImage)
+ DeviceImage_Release(self->activityProgressImage);
+
+ if (NULL != self->activityBadgeBitmap)
+ DeleteObject(self->activityBadgeBitmap);
+
+ if (NULL != self->activityFont)
+ DeleteObject(self->activityFont);
+
+ ListWidget_TooltipDestroy(self->tooltip);
+
+ if (NULL != self->commands)
+ {
+ ListWidget_DestroyAllCommands(self->commands, self->commandsCount);
+ free(self->commands);
+ }
+
+ free(self);
+
+ sliderWindow = MANAGERVIEW_GET_ZOOM_SLIDER(GetParent(hwnd));
+ if (NULL != sliderWindow)
+ {
+ ShowWindow(sliderWindow, SW_HIDE);
+ }
+}
+
+static void
+ListWidget_LayoutCb(ListWidget *self, HWND hwnd, WidgetStyle *style,
+ const RECT *clientRect, SIZE *viewSize, BOOL redraw)
+{
+ size_t iCategory, iGroup, iItem;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+ ListWidgetCategoryMetric categoryMetrics;
+ size_t itemsInLine;
+ long viewWidth, itemHeight, lineHeight;
+ long itemTextWidth, itemTextHeightMax, textHeight, fontHeight;
+ long categoryHeight, categorySpacing, categorySpacingCollapsed;
+ SIZE itemSpacing, widgetSize, itemSize;
+ POINT widgetOffset, categoryOffset, itemOffset;
+ RECT elementRect;
+ HDC targetDC;
+ HFONT targetPrevFont;
+ TEXTMETRIC textMetrics;
+
+ if (NULL == style)
+ return;
+
+ viewWidth = RECTWIDTH(*clientRect);
+
+ if (FALSE == ListWidget_CalculateItemBaseSize(self, style, &itemSize, &itemTextWidth))
+ SetSizeEmpty(&itemSize);
+
+ self->itemWidth = itemSize.cx;
+
+ WIDGETSTYLE_DLU_TO_HORZ_PX_MIN(widgetOffset.x, style, LISTWIDGET_OFFSET_LEFT_DLU, 1);
+ WIDGETSTYLE_DLU_TO_VERT_PX_MIN(widgetOffset.y, style, LISTWIDGET_OFFSET_TOP_DLU, 1);
+ WIDGETSTYLE_DLU_TO_HORZ_PX_MIN(categoryOffset.x, style, LISTWIDGET_CATEGORY_OFFSET_LEFT_DLU, 1);
+ WIDGETSTYLE_DLU_TO_VERT_PX_MIN(categoryOffset.y, style, LISTWIDGET_CATEGORY_OFFSET_TOP_DLU, 1);
+ WIDGETSTYLE_DLU_TO_VERT_PX_MIN(categorySpacing, style, LISTWIDGET_CATEGORY_SPACING_DLU, 1);
+ WIDGETSTYLE_DLU_TO_VERT_PX_MIN(categorySpacingCollapsed, style, LISTWIDGET_CATEGORY_SPACING_COLLAPSED_DLU, 1);
+
+ WIDGETSTYLE_DLU_TO_HORZ_PX_MIN(itemOffset.x, style, LISTWIDGET_ITEM_OFFSET_LEFT_DLU, 1);
+ WIDGETSTYLE_DLU_TO_VERT_PX_MIN(itemOffset.y, style, LISTWIDGET_ITEM_OFFSET_TOP_DLU, 1);
+
+ WIDGETSTYLE_DLU_TO_HORZ_PX_MIN(itemSpacing.cx, style, LISTWIDGET_ITEM_SPACING_HORZ_DLU, 1);
+ WIDGETSTYLE_DLU_TO_VERT_PX_MIN(itemSpacing.cy, style, LISTWIDGET_ITEM_SPACING_VERT_DLU, 1);
+
+ self->itemsPerLine = ((viewWidth - (widgetOffset.x + itemOffset.x))/* + itemSpacing.cx*/) / (itemSize.cx + itemSpacing.cx);
+ self->itemsPerLine = MAX(self->itemsPerLine, 1);
+
+ itemsInLine = 0;
+ for (iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ category = self->categories[iCategory];
+ if (FALSE == category->collapsed)
+ {
+ for(iGroup = 0; iGroup < category->groups.size(); iGroup++)
+ {
+ group = category->groups[iGroup];
+ if (itemsInLine < group->items.size())
+ itemsInLine = group->items.size();
+ }
+ }
+ }
+
+ if (self->itemsPerLine < itemsInLine && self->itemsPerLine > 1)
+ itemSpacing.cx = (LONG)(((viewWidth - (widgetOffset.x + itemOffset.x)) - (itemSize.cx * self->itemsPerLine))/self->itemsPerLine);
+
+ if (itemsInLine < self->itemsPerLine)
+ self->itemsPerLine = itemsInLine;
+
+ widgetSize.cx = widgetOffset.x + itemOffset.x + itemSize.cx;
+ widgetSize.cy = widgetOffset.y;
+
+ targetDC = GetDCEx(hwnd, NULL, DCX_CACHE | DCX_NORESETATTRS);
+ if (NULL != targetDC)
+ {
+ targetPrevFont = SelectFont(targetDC, WIDGETSTYLE_CATEGORY_FONT(style));
+ categoryHeight = Graphics_GetFontHeight(targetDC);
+
+ SelectFont(targetDC, WIDGETSTYLE_TEXT_FONT(style));
+ fontHeight = Graphics_GetFontHeight(targetDC);
+ itemTextHeightMax = LISTWIDGET_ITEM_TITLE_MAX_LINES * fontHeight;
+ }
+ else
+ {
+ targetPrevFont = NULL;
+ itemTextHeightMax = 0;
+ categoryHeight = 0;
+ fontHeight = 0;
+ }
+
+ if (NULL == targetDC ||
+ FALSE == GetTextMetrics(targetDC, &textMetrics))
+ {
+ ZeroMemory(&textMetrics, sizeof(textMetrics));
+ }
+
+ if (FALSE != ListWidget_GetCategoryMetrics(style, &categoryMetrics))
+ {
+ if (categoryHeight < categoryMetrics.minHeight)
+ categoryHeight = categoryMetrics.minHeight;
+
+ categoryHeight += categoryMetrics.offsetTop + categoryMetrics.offsetBottom +
+ categoryMetrics.lineHeight + categoryMetrics.lineOffsetTop;
+ }
+
+ for (iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ category = self->categories[iCategory];
+
+ SetRect(&category->rect, 0, 0, viewWidth, categoryHeight);
+
+ long offsetX = clientRect->left + categoryOffset.x;
+ long offsetY = clientRect->top + widgetSize.cy;
+ if (0 == iCategory)
+ offsetY += categoryOffset.y;
+ else
+ {
+ if (FALSE == self->categories[iCategory - 1]->collapsed)
+ offsetY += categorySpacing;
+ else
+ offsetY += categorySpacingCollapsed;
+ }
+
+ OffsetRect(&category->rect, offsetX, offsetY);
+
+ widgetSize.cy = category->rect.bottom - clientRect->top;
+ size_t itemsInCategory = 0;
+
+ if (FALSE == category->collapsed)
+ {
+ for(iGroup = 0; iGroup < category->groups.size(); iGroup++)
+ {
+ group = category->groups[iGroup];
+ itemsInLine = 1;
+ lineHeight = 0;
+
+ if (0 != group->items.size())
+ {
+ if (0 == iGroup)
+ widgetSize.cy += itemOffset.y;
+ else
+ widgetSize.cy += itemSpacing.cy;
+
+ for(iItem = 0; iItem < group->items.size(); iItem++, itemsInLine++)
+ {
+ item = group->items[iItem];
+ if (itemsInLine > self->itemsPerLine)
+ {
+ widgetSize.cy += lineHeight + itemSpacing.cy;
+ lineHeight = 0;
+ itemsInLine = 1;
+ }
+
+ itemHeight = itemSize.cy;
+ itemsInCategory++;
+
+ if (-1 == item->titleSize.cy)
+ {
+ if (FALSE == IS_STRING_EMPTY(item->title))
+ {
+ SetRect(&elementRect, 0, 0, itemTextWidth - textMetrics.tmAveCharWidth/2, 0);
+ if (FALSE != DrawText(targetDC, item->title, -1, &elementRect,
+ DT_NOPREFIX | DT_CALCRECT | DT_EDITCONTROL | DT_WORDBREAK))
+ {
+ SetSize(&item->titleSize, RECTWIDTH(elementRect), RECTHEIGHT(elementRect));
+ item->titleSize.cx += textMetrics.tmAveCharWidth/2;
+ if (item->titleSize.cx > itemTextWidth)
+ item->titleSize.cx = itemTextWidth;
+
+
+ }
+ else
+ SetSizeEmpty(&item->titleSize);
+ }
+ else
+ {
+ SetSize(&item->titleSize, 0, textMetrics.tmHeight);
+ }
+ }
+
+ textHeight = item->titleSize.cy;
+ if (textHeight > itemTextHeightMax)
+ {
+ textHeight = itemTextHeightMax;
+ ListWidgetItem_SetTextTruncated(item);
+ }
+ itemHeight += textHeight;
+
+ SetRect(&item->rect, 0, 0, itemSize.cx, itemHeight);
+
+ offsetX = long(clientRect->left + itemOffset.x +
+ (itemsInLine - 1)*(itemSize.cx + itemSpacing.cx));
+ offsetY = clientRect->top + widgetSize.cy;
+
+ OffsetRect(&item->rect, offsetX, offsetY);
+
+ if (lineHeight < itemHeight)
+ lineHeight = itemHeight;
+ }
+
+ if (0 != lineHeight)
+ widgetSize.cy += lineHeight;
+ }
+ }
+
+ if (0 == itemsInCategory &&
+ FALSE == IS_STRING_EMPTY(category->emptyText))
+ {
+ SetRect(&category->emptyTextRect, 0, 0, viewWidth - textMetrics.tmAveCharWidth/2, 0);
+ if (FALSE != DrawText(targetDC, category->emptyText, -1, &category->emptyTextRect,
+ DT_NOPREFIX | DT_CALCRECT | DT_EDITCONTROL | DT_WORDBREAK))
+ {
+ category->emptyTextRect.right += textMetrics.tmAveCharWidth/2;
+ if (category->emptyTextRect.right > viewWidth)
+ category->emptyTextRect.right = viewWidth;
+
+ offsetX = clientRect->left + categoryOffset.x +
+ (viewWidth - category->emptyTextRect.right)/2;
+
+ offsetY = clientRect->top + widgetSize.cy + itemOffset.y + fontHeight/2;
+
+ OffsetRect(&category->emptyTextRect, offsetX, offsetY);
+
+ widgetSize.cy += RECTHEIGHT(category->emptyTextRect) + itemOffset.y + fontHeight;
+ }
+ else
+ SetRectEmpty(&category->emptyTextRect);
+ }
+ }
+ else
+ {
+ for(iGroup = 0; iGroup < category->groups.size(); iGroup++)
+ {
+ group = category->groups[iGroup];
+ for(iItem = 0; iItem < group->items.size(); iItem++, itemsInLine++)
+ {
+ item = group->items[iItem];
+ SetRectEmpty(&item->rect);
+ itemsInCategory++;
+ }
+ }
+ }
+ }
+
+ widgetSize.cy += WIDGETSTYLE_DLU_TO_VERT_PX(style, LISTWIDGET_OFFSET_BOTTOM_DLU);
+ viewSize->cx = widgetSize.cx;
+ viewSize->cy = widgetSize.cy;
+
+ size_t commandsMax = 1;
+ if (self->commandsMax != commandsMax)
+ {
+ if (NULL != self->commands)
+ {
+ ListWidget_DestroyAllCommands(self->commands, self->commandsCount);
+ free(self->commands);
+ }
+
+ self->commandsCount = 0;
+ self->commands = (ListWidgetCommand**)malloc(sizeof(ListWidgetCommand*) * commandsMax);
+
+ if (NULL != self->commands)
+ self->commandsMax = commandsMax;
+ else
+ self->commandsMax = 0;
+
+ }
+
+ if (NULL != targetDC)
+ {
+ SelectFont(targetDC, targetPrevFont);
+ ReleaseDC(hwnd, targetDC);
+ }
+}
+
+static BOOL
+ListWidget_PaintCb(ListWidget *self, HWND hwnd, WidgetStyle *style, HDC hdc, const RECT *paintRect, BOOL erase)
+{
+ size_t iCategory, iGroup, iItem;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+ HFONT prevFont;
+ FillRegion fillRegion;
+ ListWidgetPaint paint;
+ size_t itemsInCategory;
+
+ if (FALSE == ListWidgetPaint_Initialize(&paint, self, style, hwnd, hdc, paintRect, erase))
+ return FALSE;
+
+ SetTextColor(hdc, style->textColor);
+ SetBkColor(hdc, style->backColor);
+ SetBkMode(hdc, TRANSPARENT);
+ prevFont = SelectFont(hdc, style->textFont);
+
+ FillRegion_Init(&fillRegion, paintRect);
+
+ if (FALSE != erase)
+ {
+ for (iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ category = self->categories[iCategory];
+
+ FillRegion_ExcludeRect(&fillRegion, &category->rect);
+
+ if (FALSE == category->collapsed)
+ {
+ itemsInCategory = 0;
+ for(iGroup = 0; iGroup < category->groups.size(); iGroup++)
+ {
+ group = category->groups[iGroup];
+ for(iItem = 0; iItem < group->items.size(); iItem++)
+ {
+ item = group->items[iItem];
+ itemsInCategory++;
+ FillRegion_ExcludeRect(&fillRegion, &item->rect);
+ }
+ }
+
+ if (0 == itemsInCategory &&
+ FALSE == IS_STRING_EMPTY(category->emptyText))
+ {
+ FillRegion_ExcludeRect(&fillRegion, &category->emptyTextRect);
+ }
+ }
+ }
+ FillRegion_BrushFill(&fillRegion, hdc, style->backBrush);
+ FillRegion_SetEmpty(&fillRegion);
+ }
+
+
+ for (iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ category = self->categories[iCategory];
+
+ if (FALSE == ListWidgetPaint_DrawCategory(&paint, category))
+ FillRegion_AppendRect(&fillRegion, &category->rect);
+
+ if (FALSE == category->collapsed)
+ {
+ itemsInCategory = 0;
+ for(iGroup = 0; iGroup < category->groups.size(); iGroup++)
+ {
+ group = category->groups[iGroup];
+ for(iItem = 0; iItem < group->items.size(); iItem++)
+ {
+ item = group->items[iItem];
+ itemsInCategory++;
+ if (FALSE == ListWidgetPaint_DrawItem(&paint, item))
+ FillRegion_AppendRect(&fillRegion, &item->rect);
+ }
+ }
+
+ if (0 == itemsInCategory &&
+ FALSE == IS_STRING_EMPTY(category->emptyText))
+ {
+ if (FALSE == ListWidgetPaint_DrawEmptyCategoryText(&paint, category))
+ FillRegion_AppendRect(&fillRegion, &category->emptyTextRect);
+ }
+ }
+ }
+
+ FillRegion_BrushFill(&fillRegion, hdc, style->backBrush);
+ FillRegion_Uninit(&fillRegion);
+
+ SelectFont(hdc, prevFont);
+
+ ListWidgetPaint_Uninitialize(&paint);
+
+ return TRUE;
+}
+
+static void
+ListWidget_StyleColorChangedCb(ListWidget *self, HWND hwnd, WidgetStyle *style)
+{
+
+ if (NULL == self)
+ return;
+
+ if (NULL != self->spacebarBitmap)
+ {
+ DeleteObject(self->spacebarBitmap);
+ self->spacebarBitmap = NULL;
+ }
+
+ if (NULL != self->hoverBitmap)
+ {
+ DeleteObject(self->hoverBitmap);
+ self->hoverBitmap = NULL;
+ }
+
+ if (NULL != self->selectBitmap)
+ {
+ DeleteObject(self->selectBitmap);
+ self->selectBitmap = NULL;
+ }
+ if (NULL != self->inactiveSelectBitmap)
+ {
+ DeleteObject(self->inactiveSelectBitmap);
+ self->inactiveSelectBitmap = NULL;
+ }
+ if (NULL != self->arrowsBitmap)
+ {
+ DeleteObject(self->arrowsBitmap);
+ self->arrowsBitmap = NULL;
+ }
+
+
+
+ ListWidget_ResetConnnectionsColors(self, style);
+
+ if (NULL != self->titleEditor)
+ ListWidget_UpdateTitleEditorColors(self->titleEditor, style);
+
+}
+
+static void
+ListWidget_StyleFontChangedCb(ListWidget *self, HWND hwnd, WidgetStyle *style)
+{
+ size_t iCategory, iGroup, iItem;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+
+ if (NULL == self)
+ return;
+
+ for (iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ category = self->categories[iCategory];
+ category->countWidth = -1;
+ category->titleWidth = -1;
+ SetRect(&category->emptyTextRect, -1, -1, -1, -1);
+
+ for(iGroup = 0; iGroup < category->groups.size(); iGroup++)
+ {
+ group = category->groups[iGroup];
+ for(iItem = 0; iItem < group->items.size(); iItem++)
+ {
+ item = group->items[iItem];
+ SetSize(&item->titleSize, -1, -1);
+ }
+ }
+ }
+
+ ListWidget_TooltipFontChanged(self->tooltip);
+
+ if (NULL != self->titleEditor)
+ SendMessage(self->titleEditor, WM_SETFONT, (WPARAM)WIDGETSTYLE_TEXT_FONT(style), TRUE);
+
+}
+
+static BOOL
+ListWidget_MouseMoveCb(ListWidget *self, HWND hwnd, unsigned int vKeys, const POINT *cursor)
+{
+ if (self->previousMouse.x == cursor->x &&
+ self->previousMouse.y == cursor->y)
+ {
+ return TRUE;
+ }
+
+ self->previousMouse = *cursor;
+
+ if (FALSE != ListWidget_UpdateHoverEx(self, hwnd, cursor))
+ UpdateWindow(hwnd);
+
+ ListWidget_TooltipRelayMouseMessage(self->tooltip, WM_MOUSEMOVE, vKeys, cursor);
+ return TRUE;
+}
+
+static BOOL
+ListWidget_LeftButtonDownCb(ListWidget *self, HWND hwnd, unsigned int vKeys, const POINT *cursor)
+{
+ ListWidgetItem *selectedItem;
+ ListWidgetCategory *pressedCategory;
+ ListWidgetItemPart pressedPart;
+ ListWidgetCommand *command, *pressedCommand;
+ WidgetStyle *style;
+ RECT rect, pressedPartRect;
+ POINT pt, origin;
+ size_t index;
+
+ style = WIDGET_GET_STYLE(hwnd);
+ if (NULL == style)
+ return FALSE;
+
+ pt = *cursor;
+
+ if (FALSE == ListWidget_GetViewOrigin(hwnd, &origin))
+ {
+ origin.x = 0;
+ origin.y = 0;
+ }
+
+ selectedItem = NULL;
+ pressedCategory = NULL;
+ pressedPart = ListWidgetItemPart_None;
+ pressedCommand = NULL;
+
+ if (FALSE != GetClientRect(hwnd, &rect) &&
+ FALSE != PtInRect(&rect, pt))
+ {
+
+ pt.x -= origin.x;
+ pt.y -= origin.y;
+
+ pressedCategory = ListWidget_GetCategoryFromPoint(self, pt);
+
+ if (NULL == pressedCategory)
+ {
+ selectedItem = ListWidget_GetItemFromPoint(self, pt);
+ if (NULL != selectedItem)
+ {
+ ListWidgetItemMetric metrics;
+ if (FALSE != ListWidget_GetItemMetrics(style, &metrics))
+ {
+ pressedPart = ListWidgetItemPart_Command;
+ if (selectedItem == self->selectedItem)
+ pressedPart |= ListWidgetItemPart_Title;
+
+ pressedPart = ListWidget_GetItemPartFromPoint(self, selectedItem, &metrics, pt,
+ pressedPart, &pressedPartRect);
+
+ if (ListWidgetItemPart_Title == pressedPart &&
+ self->titleEditItem != selectedItem &&
+ hwnd == GetFocus() &&
+ FALSE == ListWidgetItem_IsTextEdited(selectedItem))
+ {
+ self->titleEditItem = selectedItem;
+
+ SetTimer(hwnd, LISTWIDGETTIMER_EDIT_TITLE_ID,
+ GetDoubleClickTime() + 1,
+ ListWidget_BeginTitleEditTimerCb);
+
+ }
+ }
+ }
+
+ }
+ }
+
+ if (NULL != pressedCategory)
+ {
+ ListWidgetCategoryMetric metrics;
+ if (FALSE == ListWidget_GetCategoryMetrics(style, &metrics))
+ SetRectEmpty(&rect);
+ else
+ {
+ CopyRect(&rect, &pressedCategory->rect);
+ rect.left += metrics.offsetLeft;
+ rect.top += metrics.offsetTop;
+ rect.right = rect.left + metrics.iconWidth;
+ rect.bottom -= (metrics.offsetBottom + metrics.lineHeight + metrics.lineOffsetTop);
+ }
+
+ if (FALSE == PtInRect(&rect, pt))
+ pressedCategory = NULL;
+ }
+
+ self->pressedCategory = pressedCategory;
+
+
+ ListWidget_SelectItem(self, hwnd, selectedItem, FALSE);
+
+ ListWidget_EnsureFocused(self, hwnd, FALSE);
+
+
+ if (ListWidgetItemPart_Command == pressedPart &&
+ NULL != selectedItem)
+ {
+ OffsetRect(&pressedPartRect, -selectedItem->rect.left, -selectedItem->rect.top);
+ }
+
+ index = self->commandsCount;
+
+ while(index--)
+ {
+ command = self->commands[index];
+ if (NULL == pressedCommand &&
+ ListWidgetItemPart_Command == pressedPart &&
+ ListWidget_GetCommandRectEqual(command, &pressedPartRect))
+ {
+ pressedCommand = command;
+ }
+
+ if (FALSE != ListWidget_GetCommandPressed(command) ||
+ pressedCommand == command)
+ {
+ if (FALSE != ListWidget_SetCommandPressed(pressedCommand, (pressedCommand == command)) &&
+ FALSE != ListWidget_GetCommandRect(command, &rect) &&
+ NULL != selectedItem)
+ {
+ OffsetRect(&rect, selectedItem->rect.left + origin.x, selectedItem->rect.top + origin.y);
+ InvalidateRect(hwnd, &rect, FALSE);
+ }
+ }
+ }
+
+ if (NULL != pressedCommand)
+ self->flags |= ListWidgetFlag_LButtonDownOnCommand;
+ else
+ self->flags &= ~ListWidgetFlag_LButtonDownOnCommand;
+
+
+ if (GetCapture() != hwnd)
+ SetCapture(hwnd);
+
+ UpdateWindow(hwnd);
+
+ ListWidget_TooltipHide(self->tooltip);
+
+ return TRUE;
+}
+
+static BOOL
+ListWidget_LeftButtonUpCb(ListWidget *self, HWND hwnd, unsigned int vKeys, const POINT *cursor)
+{
+ WidgetStyle *style;
+ RECT rect;
+ POINT pt, origin;
+ ListWidgetCategory *pressedCategory;
+ ListWidgetCommand *pressedCommand;
+ size_t index;
+ BOOL updateWindow;
+
+ style = WIDGET_GET_STYLE(hwnd);
+
+ pt = *cursor;
+
+ if (FALSE == ListWidget_GetViewOrigin(hwnd, &origin))
+ {
+ origin.x = 0;
+ origin.y = 0;
+ }
+
+ pressedCategory = NULL;
+ pressedCommand = NULL;
+ updateWindow = FALSE;
+
+ if (FALSE != GetClientRect(hwnd, &rect) &&
+ FALSE != PtInRect(&rect, pt))
+ {
+
+ pt.x -= origin.x;
+ pt.y -= origin.y;
+
+ pressedCategory = ListWidget_GetCategoryFromPoint(self, pt);
+
+ if (NULL == pressedCategory)
+ {
+ if (NULL != self->selectedItem &&
+ FALSE != ListWidgetItem_IsInteractive(self->selectedItem))
+ {
+
+ index = self->commandsCount;
+ while(index--)
+ {
+ if (FALSE != ListWidget_GetCommandRect(self->commands[index], &rect))
+ {
+ OffsetRect(&rect, self->selectedItem->rect.left, self->selectedItem->rect.top);
+ if (PtInRect(&rect, pt))
+ {
+ pressedCommand = self->commands[index];
+ if (FALSE != ListWidget_GetCommandDisabled(pressedCommand) ||
+ FALSE == ListWidget_GetCommandPressed(pressedCommand))
+ {
+ pressedCommand = NULL;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (NULL != self->pressedCategory)
+ {
+ if (NULL != pressedCategory)
+ {
+ ListWidgetCategoryMetric metrics;
+ if (FALSE == ListWidget_GetCategoryMetrics(style, &metrics))
+ SetRectEmpty(&rect);
+ else
+ {
+ CopyRect(&rect, &pressedCategory->rect);
+ rect.left += metrics.offsetLeft;
+ rect.top += metrics.offsetTop;
+ rect.right = rect.left + metrics.iconWidth;
+ rect.bottom -= (metrics.offsetBottom + metrics.lineHeight + metrics.lineOffsetTop);
+ }
+
+ if (FALSE == PtInRect(&rect, pt))
+ pressedCategory = NULL;
+ }
+
+ if(self->pressedCategory == pressedCategory)
+ {
+ ListWidget_ToggleCategory(pressedCategory, hwnd);
+ }
+ self->pressedCategory = NULL;
+ }
+
+ if (NULL != self->selectedItem)
+ {
+ if (NULL != pressedCommand && NULL != self->selectedItem)
+ {
+ ListWidget_SendItemCommand(self->selectedItem->name,
+ ListWidget_GetCommandName(pressedCommand),
+ hwnd, 0, TRUE);
+ }
+
+ index = self->commandsCount;
+ while(index--)
+ {
+ if (FALSE != ListWidget_GetCommandPressed(self->commands[index]))
+ {
+ if (FALSE != ListWidget_SetCommandPressed(self->commands[index], FALSE) &&
+ FALSE != ListWidget_GetCommandRect(self->commands[index], &rect))
+ {
+ OffsetRect(&rect, self->selectedItem->rect.left + origin.x, self->selectedItem->rect.top + origin.y);
+ InvalidateRect(hwnd, &rect, FALSE);
+ updateWindow = TRUE;
+ }
+ break;
+ }
+ }
+
+ if (FALSE != ListWidget_EnsureItemVisisble(self, hwnd, self->selectedItem, VISIBLE_NORMAL))
+ updateWindow = TRUE;
+ }
+
+ if (FALSE != ListWidget_UpdateHoverEx(self, hwnd, cursor))
+ updateWindow = TRUE;
+
+ if (GetCapture() == hwnd)
+ ReleaseCapture();
+
+ if (FALSE != updateWindow)
+ UpdateWindow(hwnd);
+
+ ListWidget_TooltipRelayMouseMessage(self->tooltip, WM_LBUTTONUP, vKeys, cursor);
+
+ return TRUE;
+}
+
+static BOOL
+ListWidget_LeftButtonDblClkCb(ListWidget *self, HWND hwnd, unsigned int vKeys, const POINT *cursor)
+{
+ RECT rect;
+ POINT pt = *cursor, origin;
+ ListWidgetCategory *category;
+
+ if (NULL != self->titleEditItem)
+ {
+ KillTimer(hwnd, LISTWIDGETTIMER_EDIT_TITLE_ID);
+ self->titleEditItem = NULL;
+ }
+
+ if (0 != (ListWidgetFlag_LButtonDownOnCommand & self->flags))
+ return FALSE;
+
+ if (FALSE == ListWidget_GetViewOrigin(hwnd, &origin))
+ {
+ origin.x = 0;
+ origin.y = 0;
+ }
+
+ if (FALSE != GetClientRect(hwnd, &rect) &&
+ FALSE != PtInRect(&rect, pt))
+ {
+ pt.x -= origin.x;
+ pt.y -= origin.y;
+
+ category = ListWidget_GetCategoryFromPoint(self, pt);
+ if (NULL != category)
+ {
+ ListWidget_ToggleCategory(category, hwnd);
+ self->pressedCategory = NULL;
+ return TRUE;
+ }
+
+ ListWidgetItem *item = ListWidget_GetItemFromPointEx(self, pt, &category, NULL);
+ if (NULL != item)
+ {
+ if (NULL != item &&
+ FALSE != ListWidgetItem_IsInteractive(item))
+ {
+ size_t index;
+ index = self->commandsCount;
+ while(index--)
+ {
+ if (FALSE != ListWidget_GetCommandRect(self->commands[index], &rect))
+ {
+ OffsetRect(&rect, item->rect.left, item->rect.top);
+ if (PtInRect(&rect, pt))
+ {
+ item = NULL;
+ break;
+ }
+ }
+ }
+
+ }
+
+ if (NULL != item)
+ {
+ ListWidget_CallItemAction(self, hwnd, category, item);
+ return TRUE;
+ }
+
+ }
+ }
+
+ return FALSE;
+}
+
+
+
+static BOOL
+ListWidget_RightButtonDownCb(ListWidget *self, HWND hwnd, unsigned int vKeys, const POINT *cursor)
+{
+ ListWidgetItem *selectedItem;
+ RECT rect;
+ POINT pt, origin;
+
+ pt = *cursor;
+
+ if (NULL != self->titleEditItem)
+ {
+ KillTimer(hwnd, LISTWIDGETTIMER_EDIT_TITLE_ID);
+ self->titleEditItem = NULL;
+ }
+
+ if (FALSE == ListWidget_GetViewOrigin(hwnd, &origin))
+ {
+ origin.x = 0;
+ origin.y = 0;
+ }
+
+ selectedItem = NULL;
+
+ if (FALSE != GetClientRect(hwnd, &rect) &&
+ FALSE != PtInRect(&rect, pt))
+ {
+ pt.x -= origin.x;
+ pt.y -= origin.y;
+ selectedItem = ListWidget_GetItemFromPoint(self, pt);
+ }
+
+ ListWidget_SelectItem(self, hwnd, selectedItem, TRUE);
+
+ ListWidget_EnsureFocused(self, hwnd, FALSE);
+
+ if (GetCapture() != hwnd)
+ SetCapture(hwnd);
+
+ UpdateWindow(hwnd);
+
+ ListWidget_TooltipRelayMouseMessage(self->tooltip, WM_RBUTTONDOWN, vKeys, cursor);
+
+ return FALSE; // allow defwindowproc to get this message
+}
+
+static BOOL
+ListWidget_RightButtonUpCb(ListWidget *self, HWND hwnd, unsigned int vKeys, const POINT *cursor)
+{
+ RECT rect;
+
+ if (FALSE != ListWidget_UpdateHoverEx(self, hwnd, cursor))
+ UpdateWindow(hwnd);
+
+ if (GetCapture() == hwnd)
+ ReleaseCapture();
+
+ ListWidget_TooltipRelayMouseMessage(self->tooltip, WM_RBUTTONUP, vKeys, cursor);
+
+
+ if (FALSE != GetClientRect(hwnd, &rect) &&
+ FALSE != PtInRect(&rect, *cursor))
+ {
+ ListWidgetItem *item;
+ ListWidgetItemMetric metrics;
+ POINT pt;
+
+ if (FALSE == ListWidget_GetViewOrigin(hwnd, &pt))
+ {
+ pt.x = 0;
+ pt.y = 0;
+ }
+
+ pt.x = cursor->x - pt.x;
+ pt.y = cursor->y - pt.y;
+
+ item = ListWidget_GetItemFromPoint(self, pt);
+ if (NULL != item)
+ {
+ WidgetStyle *style = WIDGET_GET_STYLE(hwnd);
+ if (NULL != style &&
+ FALSE != ListWidget_GetItemMetrics(style, &metrics))
+ {
+ ListWidgetItemPart part;
+ part = ListWidgetItemPart_Command | ListWidgetItemPart_Activity;
+ part = ListWidget_GetItemPartFromPoint(self, item, &metrics, pt, part, NULL);
+ if (ListWidgetItemPart_None != part)
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE; // allow defwindowproc to get this message
+}
+
+
+static BOOL
+ListWidget_KeyDownCb(ListWidget *self, HWND hwnd, unsigned int vKey, unsigned int flags)
+{
+ ListWidgetItem *selectedItem;
+ ListWidgetVisibleFlags visibleFlags;
+
+ selectedItem = NULL;
+ visibleFlags = VISIBLE_NORMAL/*VISIBLE_PARTIAL_OK*/;
+
+ switch(vKey)
+ {
+ case VK_HOME:
+ ListWidget_EnsureTopVisible(self, hwnd);
+ visibleFlags |= VISIBLE_ALIGN_TOP | VISIBLE_ALIGN_ALWAYS;
+ selectedItem = ListWidget_GetFirstItem(self);
+ break;
+ case VK_END:
+ ListWidget_EnsureBottomVisible(self, hwnd);
+ visibleFlags |= VISIBLE_ALIGN_BOTTOM | VISIBLE_ALIGN_ALWAYS;
+ selectedItem = ListWidget_GetLastItem(self);
+ break;
+ case VK_LEFT:
+ selectedItem = (NULL != self->selectedItem) ?
+ ListWidget_GetPreviousItem(self, self->selectedItem) :
+ ListWidget_GetLastItem(self);
+ if (NULL == selectedItem)
+ ListWidget_EnsureTopVisible(self, hwnd);
+ break;
+ case VK_RIGHT:
+ selectedItem = (NULL != self->selectedItem) ?
+ ListWidget_GetNextItem(self, self->selectedItem) :
+ ListWidget_GetFirstItem(self);
+ if (NULL == selectedItem)
+ ListWidget_EnsureBottomVisible(self, hwnd);
+ break;
+ case VK_UP:
+ selectedItem = (NULL != self->selectedItem) ?
+ ListWidget_GetPreviousLineItem(self, self->selectedItem) :
+ ListWidget_GetLastItem(self);
+ if (NULL == selectedItem)
+ ListWidget_EnsureTopVisible(self, hwnd);
+ break;
+ case VK_DOWN:
+ selectedItem = (NULL != self->selectedItem) ?
+ ListWidget_GetNextLineItem(self, self->selectedItem) :
+ ListWidget_GetFirstItem(self);
+ if (NULL == selectedItem)
+ ListWidget_EnsureBottomVisible(self, hwnd);
+ break;
+ case VK_PRIOR:
+ visibleFlags |= VISIBLE_ALIGN_BOTTOM;
+ selectedItem = (NULL != self->selectedItem) ?
+ ListWidget_GetPreviousPageItem(self, hwnd, self->selectedItem) :
+ ListWidget_GetLastItem(self);
+ if (NULL == selectedItem)
+ ListWidget_EnsureTopVisible(self, hwnd);
+ break;
+
+ case VK_NEXT:
+ visibleFlags |= VISIBLE_ALIGN_TOP;
+ selectedItem = (NULL != self->selectedItem) ?
+ ListWidget_GetNextPageItem(self, hwnd, self->selectedItem) :
+ ListWidget_GetFirstItem(self);
+ if (NULL == selectedItem)
+ ListWidget_EnsureBottomVisible(self, hwnd);
+
+ break;
+ case VK_RETURN:
+ if (NULL != self->selectedItem)
+ {
+ ListWidget_CallItemAction(self, hwnd, NULL, self->selectedItem);
+ ListWidget_EnsureItemVisisble(self, hwnd, self->selectedItem, visibleFlags);
+ }
+ break;
+ case VK_F2:
+ if (NULL != self->selectedItem)
+ {
+ if (NULL != self->titleEditor)
+ DestroyWindow(self->titleEditor);
+
+ self->titleEditor = ListWidget_BeginItemTitleEdit(self, hwnd, self->selectedItem);
+ }
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ if (NULL != selectedItem)
+ {
+ ListWidget_SelectItem(self, hwnd, selectedItem, FALSE);
+ ListWidget_EnsureItemVisisble(self, hwnd, self->selectedItem, visibleFlags);
+ }
+ return TRUE;
+}
+
+static BOOL
+ListWidget_KeyUpCb(ListWidget *self, HWND hwnd, unsigned int vKey, unsigned int flags)
+{
+ return FALSE;
+}
+
+static BOOL
+ListWidget_CharacterCb(ListWidget *self, HWND hwnd, unsigned int vKey, unsigned int flags)
+{
+ return FALSE;
+}
+
+static INT
+ListWidget_InputRequestCb(ListWidget *self, HWND hwnd, unsigned int vKey, MSG *message)
+{
+ INT result;
+
+ if (NULL == message)
+ return DLGC_WANTALLKEYS;
+
+ ListWidget_TooltipHide(self->tooltip);
+
+ result = DLGC_WANTCHARS;
+
+ switch(vKey)
+ {
+ case VK_LEFT:
+ case VK_RIGHT:
+ case VK_UP:
+ case VK_DOWN:
+ case VK_PRIOR:
+ case VK_NEXT:
+ case VK_END:
+ case VK_HOME:
+ case VK_RETURN:
+ result |= DLGC_WANTALLKEYS;
+ break;
+ }
+ return result;
+}
+
+
+static void
+ListWidget_FocusChangedCb(ListWidget *self, HWND hwnd, HWND focusWindow, BOOL focusReceived)
+{
+ if (NULL == self)
+ return;
+
+ if (NULL == self->selectedItem)
+ {
+ if (FALSE != focusReceived && 0 == (ListWidgetFlag_NoFocusSelect & self->flags))
+ {
+ BOOL disableSelect;
+
+ disableSelect = FALSE;
+
+ if (NULL != focusWindow)
+ {
+ HWND ancestorWindow;
+ ancestorWindow = hwnd;
+ while(NULL != ancestorWindow)
+ {
+ ancestorWindow = GetAncestor(ancestorWindow, GA_PARENT);
+ if (focusWindow == ancestorWindow)
+ {
+ wchar_t buffer[64] = {0};
+ if (0 != GetClassName(focusWindow, buffer, ARRAYSIZE(buffer)) &&
+ CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, buffer, -1, L"Winamp Gen", -1))
+ {
+ disableSelect = TRUE;
+ }
+ break;
+ }
+ }
+ }
+
+ if (FALSE == disableSelect)
+ {
+ ListWidgetItem *item;
+ item = ListWidget_GetFirstItem(self);
+ if (NULL != item)
+ ListWidget_SelectItem(self, hwnd, item, FALSE);
+ }
+ }
+ }
+ else
+ {
+ POINT origin;
+ RECT rect;
+
+ CopyRect(&rect, &self->selectedItem->rect);
+ if (FALSE != ListWidget_GetViewOrigin(hwnd, &origin))
+ OffsetRect(&rect, origin.x, origin.y);
+
+ InvalidateRect(hwnd, &rect, FALSE);
+ UpdateWindow(hwnd);
+ }
+}
+
+static BOOL
+ListWidget_ContextMenuCb(ListWidget *self, HWND hwnd, HWND targetWindow, const POINT *cursor)
+{
+ POINT pt;
+ ListWidgetItem *item;
+
+ if (NULL == self)
+ return FALSE;
+
+
+ if (NULL == cursor ||
+ (-1 == cursor->x && -1 == cursor->y))
+ {
+ if (hwnd != targetWindow)
+ return FALSE;
+
+ item = self->selectedItem;
+ if (NULL != item)
+ {
+ POINT origin;
+ pt.x = RECTWIDTH(item->rect);
+ pt.x = item->rect.left + pt.x/2 + pt.x%2;
+ pt.y = RECTHEIGHT(item->rect);
+ pt.y = item->rect.top + pt.y/2 + pt.y%2;
+
+ if (FALSE != ListWidget_GetViewOrigin(hwnd, &origin))
+ {
+ pt.x += origin.x;
+ pt.y += origin.y;
+ }
+ }
+ else
+ {
+ RECT rect;
+ GetClientRect(hwnd, &rect);
+ pt.x = rect.left + 2;
+ pt.y = rect.top + 2;
+ }
+
+ MapWindowPoints(hwnd, HWND_DESKTOP, &pt, 1);
+ }
+ else
+ {
+ POINT test;
+ pt = *cursor;
+
+ if (FALSE == ListWidget_GetViewOrigin(hwnd, &test))
+ {
+ test = pt;
+ }
+ else
+ {
+ test.x = pt.x - test.x;
+ test.y = pt.y - test.y;
+ }
+ MapWindowPoints(HWND_DESKTOP, hwnd, &test, 1);
+ item = ListWidget_GetItemFromPoint(self, test);
+ }
+
+ if (NULL == item)
+ ListWidget_DisplayContextMenu(self, hwnd, pt);
+ else
+ ListWidget_DisplayItemContextMenu(self, hwnd, item, pt);
+
+ return TRUE;
+};
+
+
+
+static void
+ListWidget_ZoomChangingCb(ListWidget *self, HWND hwnd, NMTRBTHUMBPOSCHANGING *zoomInfo)
+{
+ double pos, height, width;
+ int cx, cy;
+
+ if (NULL == self)
+ return;
+
+ pos = (double)(int)zoomInfo->dwPos;
+ if (0 == pos)
+ {
+ height = LISTWIDGET_IMAGE_DEFAULT_HEIGHT;
+ }
+ else if (pos < 0)
+ {
+ height = ((LISTWIDGET_IMAGE_DEFAULT_HEIGHT - LISTWIDGET_IMAGE_MIN_HEIGHT) * (100.0 + pos))/100.0;
+ height += LISTWIDGET_IMAGE_MIN_HEIGHT;
+ height = floor(height);
+ }
+ else
+ {
+ height = ((LISTWIDGET_IMAGE_MAX_HEIGHT - LISTWIDGET_IMAGE_DEFAULT_HEIGHT) * pos)/100.0;
+ height += LISTWIDGET_IMAGE_DEFAULT_HEIGHT;
+ height = ceil(height);
+
+ }
+
+ width = (height * LISTWIDGET_IMAGE_DEFAULT_WIDTH)/ LISTWIDGET_IMAGE_DEFAULT_HEIGHT;
+
+ cx = (int)(width + 0.5);
+ cy = (int)(height + 0.5);
+
+ if (self->imageSize.cx == cx &&
+ self->imageSize.cy == cy)
+ {
+ return;
+ }
+
+ ListWidget_SetImageSize(self, hwnd, cx, cy, TRUE);
+
+// aTRACE_FMT("zoom changing: pos = %d, width = %d, height = %d\r\n", zoomInfo->dwPos, cx, cy);
+}
+
+static void
+ListWidget_ScrollCb(ListWidget *self, HWND hwnd, int *dx, int *dy)
+{
+ if (NULL != self->titleEditor)
+ {
+ DestroyWindow(self->titleEditor);
+ self->titleEditor = NULL;
+ }
+
+ if (FALSE != ListWidget_UpdateHover(self, hwnd))
+ UpdateWindow(hwnd);
+
+
+ ListWidget_TooltipHide(self->tooltip);
+}
+
+
+static BOOL
+ListWidget_NotifyCb(ListWidget *self, HWND hwnd, NMHDR *pnmh, LRESULT *result)
+{
+ if (FALSE != ListWidget_TooltipProcessNotification(self, self->tooltip, pnmh, result))
+ return TRUE;
+
+ return FALSE;
+}
+HWND
+ListWidget_CreateWindow(HWND parentWindow, int x, int y, int width, int height, BOOL border, unsigned int controlId)
+{
+ const static WidgetInterface widgetInterface =
+ {
+ (WidgetInitCallback)ListWidget_InitCb,
+ (WidgetDestroyCallback)ListWidget_DestroyCb,
+ (WidgetLayoutCallback)ListWidget_LayoutCb,
+ (WidgetPaintCallback)ListWidget_PaintCb,
+ (WidgetStyleCallback)ListWidget_StyleColorChangedCb,
+ (WidgetStyleCallback)ListWidget_StyleFontChangedCb,
+ (WidgetMouseCallback)ListWidget_MouseMoveCb,
+ (WidgetMouseCallback)ListWidget_LeftButtonDownCb,
+ (WidgetMouseCallback)ListWidget_LeftButtonUpCb,
+ (WidgetMouseCallback)ListWidget_LeftButtonDblClkCb,
+ (WidgetMouseCallback)ListWidget_RightButtonDownCb,
+ (WidgetMouseCallback)ListWidget_RightButtonUpCb,
+ (WidgetKeyCallback)ListWidget_KeyDownCb,
+ (WidgetKeyCallback)ListWidget_KeyUpCb,
+ (WidgetKeyCallback)ListWidget_CharacterCb,
+ (WidgetInputCallback)ListWidget_InputRequestCb,
+ (WidgetFocusCallback)ListWidget_FocusChangedCb,
+ (WidgetMenuCallback)ListWidget_ContextMenuCb,
+ (WidgetZoomCallback)ListWidget_ZoomChangingCb,
+ (WidgetScrollCallback)NULL, /*scrollBefore*/
+ (WidgetScrollCallback)ListWidget_ScrollCb,
+ (WidgetNotifyCallback)ListWidget_NotifyCb,
+ };
+
+ return Widget_CreateWindow(WIDGET_TYPE_LIST,
+ &widgetInterface,
+ NULL,
+ (FALSE != border) ? WS_EX_CLIENTEDGE : 0,
+ WS_TABSTOP,
+ x, y, width, height,
+ parentWindow,
+ controlId, 0L);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/listWidget.h b/Src/Plugins/Library/ml_devices/listWidget.h
new file mode 100644
index 00000000..01713f58
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/listWidget.h
@@ -0,0 +1,23 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_LIST_WIDGET_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_LIST_WIDGET_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+#define WIDGET_TYPE_LIST 3
+
+HWND
+ListWidget_CreateWindow(HWND parentWindow,
+ int x,
+ int y,
+ int width,
+ int height,
+ BOOL border,
+ unsigned int controlId);
+
+
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_LIST_WIDGET_HEADER
diff --git a/Src/Plugins/Library/ml_devices/listWidgetCategory.cpp b/Src/Plugins/Library/ml_devices/listWidgetCategory.cpp
new file mode 100644
index 00000000..0bb9cd18
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/listWidgetCategory.cpp
@@ -0,0 +1,202 @@
+#include "main.h"
+#include "./listWidgetInternal.h"
+#include <algorithm>
+
+#define LISTWIDGETCATEGORY_OFFSET_LEFT_DLU 3
+#define LISTWIDGETCATEGORY_OFFSET_TOP_DLU 0
+#define LISTWIDGETCATEGORY_OFFSET_RIGHT_DLU 4
+#define LISTWIDGETCATEGORY_OFFSET_BOTTOM_DLU 0
+#define LISTWIDGETCATEGORY_LINE_OFFSET_TOP_DLU 0
+#define LISTWIDGETCATEGORY_LINE_HEIGHT_DLU 1
+#define LISTWIDGETCATEGORY_TITLE_OFFSET_LEFT_DLU 4
+#define LISTWIDGETCATEGORY_MIN_HEIGHT_DLU 9
+#define LISTWIDGETCATEGORY_ARROW_WIDTH_PX 8
+#define LISTWIDGETCATEGORY_ARROW_HEIGHT_PX 8
+
+
+ListWidgetCategory *
+ListWidget_CreateCategory(const char *name, const wchar_t *title, BOOL collapsed)
+{
+ ListWidgetCategory *category;
+
+ if (NULL == name)
+ return NULL;
+
+ category = new ListWidgetCategory();
+ if(NULL == category)
+ return NULL;
+
+ category->name = AnsiString_Duplicate(name);
+ category->title = String_Duplicate(title);
+ category->collapsed = collapsed;
+ category->countString = NULL;
+ category->countWidth = -1;
+ category->titleWidth = -1;
+ category->emptyText = NULL;
+ SetRect(&category->emptyTextRect, -1, -1, -1, -1);
+
+ return category;
+}
+
+void
+ListWidget_DestroyCategory(ListWidgetCategory *category)
+{
+ size_t index;
+ if (NULL == category)
+ return;
+
+ index = category->groups.size();
+ while(index--)
+ {
+ ListWidget_DestroyGroup(category->groups[index]);
+ }
+
+ AnsiString_Free(category->name);
+ String_Free(category->title);
+ String_Free(category->countString);
+ String_Free(category->emptyText);
+
+ delete category;
+}
+
+BOOL
+ListWidget_GetCategoryMetrics(WidgetStyle *style, ListWidgetCategoryMetric *metrics)
+{
+ long test;
+
+ if (NULL == metrics || NULL == style)
+ return FALSE;
+
+ WIDGETSTYLE_DLU_TO_HORZ_PX_MIN(metrics->offsetLeft, style, LISTWIDGETCATEGORY_OFFSET_LEFT_DLU, 1);
+ WIDGETSTYLE_DLU_TO_VERT_PX_MIN(metrics->offsetTop, style, LISTWIDGETCATEGORY_OFFSET_TOP_DLU, 1);
+ WIDGETSTYLE_DLU_TO_HORZ_PX_MIN(metrics->offsetRight, style, LISTWIDGETCATEGORY_OFFSET_RIGHT_DLU, 1);
+ WIDGETSTYLE_DLU_TO_VERT_PX_MIN(metrics->offsetBottom, style, LISTWIDGETCATEGORY_OFFSET_BOTTOM_DLU, 1);
+
+ WIDGETSTYLE_DLU_TO_HORZ_PX_MIN(metrics->titleOffsetLeft, style, LISTWIDGETCATEGORY_TITLE_OFFSET_LEFT_DLU, 2);
+
+ WIDGETSTYLE_DLU_TO_VERT_PX_MIN(metrics->lineOffsetTop, style, LISTWIDGETCATEGORY_LINE_OFFSET_TOP_DLU, 1);
+ WIDGETSTYLE_DLU_TO_VERT_PX_MIN(metrics->minHeight, style, LISTWIDGETCATEGORY_MIN_HEIGHT_DLU, 1);
+
+ #if (0 != LISTWIDGETCATEGORY_LINE_HEIGHT_DLU)
+ {
+ metrics->lineHeight = WIDGETSTYLE_DLU_TO_VERT_PX(style, LISTWIDGETCATEGORY_LINE_HEIGHT_DLU);
+ metrics->lineHeight = metrics->lineHeight /2;
+ if (0 == metrics->lineHeight)
+ metrics->lineHeight = 1;
+ }
+ #endif
+
+
+ metrics->iconWidth = LISTWIDGETCATEGORY_ARROW_WIDTH_PX;
+ metrics->iconHeight = LISTWIDGETCATEGORY_ARROW_HEIGHT_PX;
+
+ test = metrics->iconHeight +
+ metrics->offsetTop + metrics->offsetBottom +
+ metrics->lineHeight + metrics->lineOffsetTop;
+
+ if (metrics->minHeight < test)
+ metrics->minHeight = test;
+
+ return TRUE;
+}
+
+ListWidgetCategory *
+ListWidget_FindCategory(ListWidget *self, const char *name)
+{
+ if (NULL == self || NULL == name)
+ return NULL;
+
+ for (size_t iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ ListWidgetCategory *category = self->categories[iCategory];
+ if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, 0, name, -1, category->name, -1))
+ return category;
+ }
+ return NULL;
+}
+
+ListWidgetCategory *
+ListWidget_GetCategoryFromPoint(ListWidget *self, POINT point)
+{
+ size_t iCategory;
+ ListWidgetCategory *category;
+
+ if (NULL == self)
+ return NULL;
+
+ for (iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ category = self->categories[iCategory];
+ if (FALSE != PtInRect(&category->rect, point))
+ return category;
+ }
+ return NULL;
+}
+
+BOOL
+ListWidget_ToggleCategory(ListWidgetCategory *category, HWND hwnd)
+{
+ if (NULL == category)
+ return FALSE;
+
+ category->collapsed = !category->collapsed;
+
+ if (NULL != hwnd)
+ ListWidget_UpdateLayout(hwnd, ListWidgetLayout_UpdateNow);
+
+ return TRUE;
+}
+
+void
+ListWidget_ResetCategoryCounter(ListWidgetCategory *category)
+{
+ if (NULL != category)
+ {
+ String_Free(category->countString);
+ category->countString = NULL;
+ category->countWidth = -1;
+ }
+}
+
+static bool
+ListWidget_GroupSortCb(const void *element1, const void *element2)
+{
+ ListWidgetGroup *group1;
+ ListWidgetGroup *group2;
+ int result;
+
+ group1 = *(ListWidgetGroup**)element1;
+ group2 = *(ListWidgetGroup**)element2;
+
+ result = CompareString(LOCALE_USER_DEFAULT, 0, group1->title, -1, group2->title, -1);
+ if (CSTR_EQUAL == result || 0 == result)
+ result = CompareStringA(CSTR_INVARIANT, 0, group1->name, -1, group2->name, -1);
+
+ return (result == CSTR_LESS_THAN);
+
+}
+
+
+void
+ListWidget_SortCategory(ListWidgetCategory *category)
+{
+ if (category->groups.size())
+ {
+ //qsort(category->groups.first(), category->groups.size(), sizeof(ListWidgetGroup**), ListWidget_GroupSortCb);
+ std::sort(category->groups.begin(), category->groups.end(), ListWidget_GroupSortCb);
+ }
+}
+
+BOOL
+ListWidget_SetCategoryEmptyText(ListWidgetCategory *category, const wchar_t *text)
+{
+ if (NULL == category)
+ return FALSE;
+
+ String_Free(category->emptyText);
+ category->emptyText = String_Duplicate(text);
+
+ SetRect(&category->emptyTextRect, -1, -1, -1, -1);
+
+ return TRUE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/listWidgetCommand.cpp b/Src/Plugins/Library/ml_devices/listWidgetCommand.cpp
new file mode 100644
index 00000000..6a8707bd
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/listWidgetCommand.cpp
@@ -0,0 +1,392 @@
+#include "main.h"
+#include "./listWidgetInternal.h"
+
+#define REQUEST_STRING ((wchar_t*)1)
+#define REQUEST_IMAGE ((DeviceImage*)1)
+
+typedef struct ListWidgetCommand
+{
+ char *name;
+ ListWidgetCommandState state;
+ wchar_t *title;
+ wchar_t *description;
+ DeviceImage *imageLarge;
+ DeviceImage *imageSmall;
+ RECT rect;
+} ListWidgetCommand;
+
+ListWidgetCommand *
+ListWidget_CreateCommand(const char *name, BOOL primary, BOOL disabled)
+{
+ ListWidgetCommand *self;
+
+ if (NULL == name)
+ return NULL;
+
+ self = (ListWidgetCommand*)malloc(sizeof(ListWidgetCommand));
+ if (NULL == self)
+ return NULL;
+
+ ZeroMemory(self, sizeof(ListWidgetCommand));
+
+ self->name = AnsiString_Duplicate(name);
+ self->state = ListWidgetCommandState_Normal;
+
+ if (NULL != self->name)
+ {
+ if (FALSE != primary)
+ self->state |= ListWidgetCommandState_Primary;
+
+ if (FALSE != disabled)
+ self->state |= ListWidgetCommandState_Disabled;
+
+ }
+ else
+ {
+ ListWidget_DestroyCommand(self);
+ return NULL;
+ }
+
+ self->title = REQUEST_STRING;
+ self->description = REQUEST_STRING;
+ self->imageLarge = REQUEST_IMAGE;
+ self->imageSmall = REQUEST_IMAGE;
+
+ return self;
+}
+
+void
+ListWidget_DestroyCommand(ListWidgetCommand *command)
+{
+ if (NULL == command)
+ return;
+
+ AnsiString_Free(command->name);
+
+ if (NULL != command->imageLarge && REQUEST_IMAGE != command->imageLarge)
+ DeviceImage_Release(command->imageLarge);
+
+ if (NULL != command->imageSmall && REQUEST_IMAGE != command->imageSmall)
+ DeviceImage_Release(command->imageSmall);
+
+ if (REQUEST_STRING != command->title)
+ String_Free(command->title);
+
+ if (REQUEST_STRING != command->description)
+ String_Free(command->description);
+
+ free(command);
+}
+
+size_t
+ListWigdet_GetDeviceCommands(ListWidgetCommand **buffer, size_t bufferMax, ifc_device *device)
+{
+ size_t count;
+ ifc_devicesupportedcommandenum *enumerator;
+ ifc_devicesupportedcommand *command;
+ DeviceCommandFlags flags;
+ ListWidgetCommand *widgetCommand;
+ BOOL primaryFound;
+
+ if (NULL == buffer || bufferMax < 1 || NULL == device)
+ return 0;
+
+ if (FAILED(device->EnumerateCommands(&enumerator, DeviceCommandContext_View)))
+ return 0;
+
+ count = 0;
+ primaryFound = FALSE;
+
+ while(S_OK == enumerator->Next(&command, 1, NULL))
+ {
+ if(FAILED(command->GetFlags(&flags)))
+ flags = DeviceCommandFlag_None;
+
+ if (0 == (DeviceCommandFlag_Hidden & flags))
+ {
+ widgetCommand = ListWidget_CreateCommand(command->GetName(),
+ (0 != (DeviceCommandFlag_Primary & flags)),
+ (0 != (DeviceCommandFlag_Disabled & flags)));
+ if (NULL != widgetCommand)
+ {
+ if (0 != (DeviceCommandFlag_Primary & flags))
+ {
+ if (count == bufferMax)
+ {
+ ListWidget_DestroyCommand(buffer[count-1]);
+ count--;
+ }
+
+ if (count > 0)
+ MoveMemory(&buffer[1], buffer, sizeof(ListWidgetCommand*) * count);
+
+ buffer[0] = widgetCommand;
+ primaryFound = TRUE;
+ count++;
+ }
+ else
+ {
+ if (count < bufferMax)
+ {
+ buffer[count] = widgetCommand;
+ count++;
+ }
+ }
+ }
+ }
+ command->Release();
+
+ if (count == bufferMax && FALSE != primaryFound)
+ break;
+ }
+
+ enumerator->Release();
+
+ return count;
+}
+
+void
+ListWidget_DestroyAllCommands(ListWidgetCommand **buffer, size_t bufferMax)
+{
+ if (NULL == buffer)
+ return;
+
+ while(bufferMax--)
+ ListWidget_DestroyCommand(buffer[bufferMax]);
+}
+
+
+
+const char *
+ListWidget_GetCommandName(ListWidgetCommand *command)
+{
+ return (NULL != command) ? command->name : NULL;
+}
+
+const wchar_t *
+ListWidget_GetCommandTitle(ListWidgetCommand *command)
+{
+ if (NULL == command || NULL == command->title)
+ return NULL;
+
+ if (REQUEST_STRING == command->title)
+ {
+ ifc_devicecommand *info;
+
+ command->title = NULL;
+
+ if (NULL != WASABI_API_DEVICES &&
+ S_OK == WASABI_API_DEVICES->CommandFind(command->name, &info))
+ {
+ wchar_t buffer[512] = {0};
+
+ if (SUCCEEDED(info->GetDisplayName(buffer, ARRAYSIZE(buffer))))
+ command->title = String_Duplicate(buffer);
+
+ info->Release();
+ }
+ }
+
+ return command->title;
+}
+
+const wchar_t *
+ListWidget_GetCommandDescription(ListWidgetCommand *command)
+{
+ if (NULL == command || NULL == command->description)
+ return NULL;
+
+ if (REQUEST_STRING == command->description)
+ {
+ ifc_devicecommand *info;
+
+ command->description = NULL;
+
+ if (NULL != WASABI_API_DEVICES &&
+ S_OK == WASABI_API_DEVICES->CommandFind(command->name, &info))
+ {
+ wchar_t buffer[1024] = {0};
+
+ if (SUCCEEDED(info->GetDescription(buffer, ARRAYSIZE(buffer))))
+ command->description = String_Duplicate(buffer);
+
+ info->Release();
+ }
+ }
+ return command->description;
+}
+
+HBITMAP
+ListWidget_GetCommandLargeBitmap(WidgetStyle *style, ListWidgetCommand *command, int width, int height)
+{
+ if (NULL == style || NULL == command || NULL == command->imageLarge)
+ return NULL;
+
+ if (REQUEST_IMAGE == command->imageLarge)
+ {
+ ifc_devicecommand *info;
+
+ command->imageLarge = NULL;
+
+ if (NULL != WASABI_API_DEVICES &&
+ S_OK == WASABI_API_DEVICES->CommandFind(command->name, &info))
+ {
+ wchar_t buffer[MAX_PATH * 2] = {0};
+
+ if (FAILED(info->GetIcon(buffer, ARRAYSIZE(buffer), width, height)))
+ buffer[0] = L'\0';
+
+ info->Release();
+
+ if (L'\0' != buffer[0])
+ {
+ command->imageLarge = DeviceImageCache_GetImage(Plugin_GetImageCache(), buffer,
+ width, height, NULL, NULL);
+
+ }
+ }
+ }
+
+ return DeviceImage_GetBitmap(command->imageLarge, DeviceImage_Normal);
+}
+
+HBITMAP
+ListWidget_GetCommandSmallBitmap(WidgetStyle *style, ListWidgetCommand *command, int width, int height)
+{
+ if (NULL == style || NULL == command || NULL == command->imageSmall)
+ return NULL;
+
+ if (REQUEST_IMAGE == command->imageSmall)
+ {
+ ifc_devicecommand *info;
+
+ command->imageSmall = NULL;
+
+ if (NULL != WASABI_API_DEVICES &&
+ S_OK == WASABI_API_DEVICES->CommandFind(command->name, &info))
+ {
+ wchar_t buffer[MAX_PATH * 2] = {0};
+
+ if (FAILED(info->GetIcon(buffer, ARRAYSIZE(buffer), width, height)))
+ buffer[0] = L'\0';
+
+ info->Release();
+
+ if (L'\0' != buffer[0])
+ {
+ command->imageSmall = DeviceImageCache_GetImage(Plugin_GetImageCache(), buffer,
+ width, height, NULL, NULL);
+
+ }
+ }
+ }
+
+ return DeviceImage_GetBitmap(command->imageSmall, DeviceImage_Normal);
+}
+
+BOOL
+ListWidget_ResetCommandImages(ListWidgetCommand *command)
+{
+ if (NULL == command)
+ return FALSE;
+
+ if (REQUEST_IMAGE != command->imageLarge)
+ {
+ if (NULL != command->imageLarge)
+ DeviceImage_Release(command->imageLarge);
+ command->imageLarge = REQUEST_IMAGE;
+ }
+
+ if (REQUEST_IMAGE != command->imageSmall)
+ {
+ if (NULL != command->imageSmall)
+ DeviceImage_Release(command->imageSmall);
+ command->imageSmall = REQUEST_IMAGE;
+ }
+
+ return TRUE;
+}
+
+BOOL
+ListWidget_GetCommandRect(ListWidgetCommand *command, RECT *rect)
+{
+ if (NULL == command || NULL == rect)
+ return FALSE;
+
+ return CopyRect(rect, &command->rect);
+}
+
+BOOL
+ListWidget_SetCommandRect(ListWidgetCommand *command, const RECT *rect)
+{
+ if (NULL == command || NULL == rect)
+ return FALSE;
+
+ return CopyRect(&command->rect, rect);
+}
+
+BOOL
+ListWidget_GetCommandRectEqual(ListWidgetCommand *command, const RECT *rect)
+{
+ if (NULL == command || NULL == rect)
+ return FALSE;
+
+ return EqualRect(&command->rect, rect);
+}
+
+BOOL
+ListWidget_GetCommandPrimary(ListWidgetCommand *command)
+{
+ return (NULL == command ||
+ (0 != (ListWidgetCommandState_Primary & command->state)));
+}
+
+
+BOOL
+ListWidget_GetCommandDisabled(ListWidgetCommand *command)
+{
+ return (NULL == command ||
+ (0 != (ListWidgetCommandState_Disabled & command->state)));
+}
+
+
+BOOL
+ListWidget_GetCommandPressed(ListWidgetCommand *command)
+{
+ return (NULL == command ||
+ (0 != (ListWidgetCommandState_Pressed & command->state)));
+}
+
+BOOL
+ListWidget_EnableCommand(ListWidgetCommand *command, BOOL enable)
+{
+ if (NULL == command)
+ return FALSE;
+
+ if ((FALSE == enable) == (0 != (ListWidgetCommandState_Disabled & command->state)))
+ return FALSE;
+
+ if (FALSE == enable)
+ command->state |= ListWidgetCommandState_Disabled;
+ else
+ command->state &= ~ListWidgetCommandState_Disabled;
+
+ return TRUE;
+}
+
+BOOL
+ListWidget_SetCommandPressed(ListWidgetCommand *command, BOOL pressed)
+{
+ if (NULL == command)
+ return FALSE;
+
+ if ((FALSE == pressed) == (0 == (ListWidgetCommandState_Pressed & command->state)))
+ return FALSE;
+
+ if (FALSE == pressed)
+ command->state &= ~ListWidgetCommandState_Pressed;
+ else
+ command->state |= ListWidgetCommandState_Pressed;
+
+ return TRUE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/listWidgetConnection.cpp b/Src/Plugins/Library/ml_devices/listWidgetConnection.cpp
new file mode 100644
index 00000000..f5073181
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/listWidgetConnection.cpp
@@ -0,0 +1,262 @@
+#include "main.h"
+#include "./listWidgetInternal.h"
+
+#define LISTWIDGETCONNECTION_IMAGE_WIDTH 16
+#define LISTWIDGETCONNECTION_IMAGE_HEIGHT 16
+
+#define REQUEST_STRING ((wchar_t*)1)
+#define REQUEST_IMAGE ((DeviceImage*)1)
+
+
+typedef struct ListWidgetConnection
+{
+ char *name;
+ wchar_t *title;
+ DeviceImage *image;
+} ListWidgetConnection;
+
+
+ListWidgetConnection *
+ListWidget_CreateConnection(const char *name)
+{
+ ListWidgetConnection *self;
+
+ if (NULL == name)
+ return NULL;
+
+ self = (ListWidgetConnection*)malloc(sizeof(ListWidgetConnection));
+ if (NULL == self)
+ return NULL;
+
+ ZeroMemory(self, sizeof(ListWidgetConnection));
+
+ self->name = AnsiString_Duplicate(name);
+
+ if (NULL == self->name)
+ {
+ ListWidget_DestroyConnection(self);
+ return NULL;
+ }
+
+ self->title = REQUEST_STRING;
+ self->image = REQUEST_IMAGE;
+
+ return self;
+}
+
+void
+ListWidget_DestroyConnection(ListWidgetConnection *connection)
+{
+ if (NULL == connection)
+ return;
+
+ AnsiString_Free(connection->name);
+
+ if (NULL != connection->image && REQUEST_IMAGE != connection->image)
+ DeviceImage_Release(connection->image);
+
+ if (REQUEST_STRING != connection->title)
+ String_Free(connection->title);
+
+ free(connection);
+}
+
+const wchar_t *
+ListWidget_GetConnectionTitle(ListWidgetConnection *connection)
+{
+ if (NULL == connection || NULL == connection->title)
+ return NULL;
+
+ if (REQUEST_STRING == connection->title)
+ {
+ ifc_deviceconnection *info;
+
+ connection->title = NULL;
+
+ if (NULL != WASABI_API_DEVICES &&
+ S_OK == WASABI_API_DEVICES->ConnectionFind(connection->name, &info))
+ {
+ wchar_t buffer[512] = {0};
+
+ if (SUCCEEDED(info->GetDisplayName(buffer, ARRAYSIZE(buffer))))
+ connection->title = String_Duplicate(buffer);
+
+ info->Release();
+ }
+ }
+
+ return connection->title;
+}
+
+HBITMAP
+ListWidget_GetConnectionImage(WidgetStyle *style, ListWidgetConnection *connection, int width, int height)
+{
+ if (NULL == style || NULL == connection || NULL == connection->image)
+ return NULL;
+
+ if (REQUEST_IMAGE == connection->image)
+ {
+ ifc_deviceconnection *info;
+
+ connection->image = NULL;
+
+ if (NULL != WASABI_API_DEVICES &&
+ S_OK == WASABI_API_DEVICES->ConnectionFind(connection->name, &info))
+ {
+ wchar_t buffer[MAX_PATH * 2] = {0};
+
+ if (FAILED(info->GetIcon(buffer, ARRAYSIZE(buffer), width, height)))
+ buffer[0] = L'\0';
+
+ info->Release();
+
+ if (L'\0' != buffer[0])
+ {
+ connection->image = DeviceImageCache_GetImage(Plugin_GetImageCache(), buffer,
+ width, height, NULL, NULL);
+ }
+ }
+ }
+
+ if (NULL == connection->image)
+ return NULL;
+
+ return DeviceImage_GetBitmap(connection->image, DeviceImage_Normal);
+}
+
+BOOL
+ListWidget_ConnectionResetColors(WidgetStyle *style, ListWidgetConnection *connection)
+{
+ return FALSE;
+}
+
+void
+ListWidget_ResetConnnectionsColors(ListWidget *self, WidgetStyle *style)
+{
+ return;
+}
+
+
+BOOL
+ListWidget_UpdateConnectionImageSize(ListWidgetConnection *connection, int width, int height)
+{
+ if (NULL == connection)
+ return FALSE;
+
+ if (NULL == connection->image || REQUEST_IMAGE == connection->image)
+ return TRUE;
+
+ DeviceImage_Release(connection->image);
+ connection->image = REQUEST_IMAGE;
+
+ return TRUE;
+}
+
+ListWidgetConnection *
+ListWidget_FindConnection(ListWidget *self, const char *name)
+{
+ if (NULL == self || FALSE != IS_STRING_EMPTY(name))
+ return NULL;
+
+ size_t index = self->connections.size();
+ while(index--)
+ {
+ ListWidgetConnection *connection = self->connections[index];
+ if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, 0, name, -1, connection->name, -1))
+ return connection;
+ }
+
+ return NULL;
+}
+
+BOOL
+ListWidget_AddConnection(ListWidget *self, ListWidgetConnection *connection)
+{
+ if (NULL == self || NULL == connection)
+ return FALSE;
+
+ self->connections.push_back(connection);
+ return TRUE;
+}
+
+void
+ListWidget_RemoveConnection(ListWidget *self, const char *name)
+{
+ size_t iCategory, iGroup, iItem;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+
+
+ if (NULL == self || FALSE != IS_STRING_EMPTY(name))
+ return;
+
+ size_t index = self->connections.size();
+ while(index--)
+ {
+ ListWidgetConnection *connection = self->connections[index];
+ if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, 0, name, -1, connection->name, -1))
+ {
+ for (iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ category = self->categories[iCategory];
+ for(iGroup = 0; iGroup < category->groups.size(); iGroup++)
+ {
+ group = category->groups[iGroup];
+ for(iItem = 0; iItem < group->items.size(); iItem++)
+ {
+ item = group->items[iItem];
+ if(item->connection == connection)
+ item->connection = NULL;
+ }
+ }
+ }
+
+ self->connections.erase(self->connections.begin() + index);
+ ListWidget_DestroyConnection(connection);
+ }
+ }
+}
+
+void
+ListWidget_RemoveAllConnections(ListWidget *self)
+{
+ if (NULL == self)
+ return;
+
+ size_t index = self->connections.size();
+ if (index > 0)
+ {
+ size_t iCategory, iGroup, iItem;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+
+ for (iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ category = self->categories[iCategory];
+ for(iGroup = 0; iGroup < category->groups.size(); iGroup++)
+ {
+ group = category->groups[iGroup];
+ for(iItem = 0; iItem < group->items.size(); iItem++)
+ {
+ item = group->items[iItem];
+ if(NULL != item->connection)
+ {
+ item->connection = NULL;
+ }
+ }
+ }
+ }
+
+ while(index--)
+ {
+ ListWidgetConnection *connection = self->connections[index];
+ ListWidget_DestroyConnection(connection);
+ }
+
+ self->connections.clear();
+ }
+
+
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/listWidgetGroup.cpp b/Src/Plugins/Library/ml_devices/listWidgetGroup.cpp
new file mode 100644
index 00000000..7ac29e92
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/listWidgetGroup.cpp
@@ -0,0 +1,135 @@
+#include "main.h"
+#include "./listWidgetInternal.h"
+#include <algorithm>
+
+ListWidgetGroup *
+ListWidget_CreateGroup(const char *name)
+{
+ ifc_devicetype *deviceType;
+ wchar_t buffer[1024] = {0};
+
+ if (NULL == name)
+ return NULL;
+
+ if (NULL != WASABI_API_DEVICES &&
+ S_OK == WASABI_API_DEVICES->TypeFind(name, &deviceType))
+ {
+ if (FAILED(deviceType->GetDisplayName(buffer, ARRAYSIZE(buffer))))
+ buffer[0] = L'\0';
+
+ deviceType->Release();
+ }
+ else
+ buffer[0] = L'\0';
+
+ return ListWidget_CreateGroupEx(name, buffer);
+}
+
+ListWidgetGroup *
+ListWidget_CreateGroupEx(const char *name, const wchar_t *title)
+{
+ ListWidgetGroup *group;
+
+ if (NULL == name)
+ return NULL;
+
+ group = new ListWidgetGroup();
+ if (NULL == group)
+ return NULL;
+
+ group->name = AnsiString_Duplicate(name);
+ group->title = String_Duplicate(title);
+
+ return group;
+}
+
+
+void
+ListWidget_DestroyGroup(ListWidgetGroup *group)
+{
+ size_t index;
+ if (NULL == group)
+ return;
+
+ index = group->items.size();
+ while(index--)
+ {
+ ListWidget_DestroyItem(group->items[index]);
+ }
+
+ AnsiString_Free(group->name);
+ String_Free(group->title);
+
+ delete group;
+}
+
+BOOL
+ListWidget_AddGroup( ListWidgetCategory *category, ListWidgetGroup *group)
+{
+ if (NULL == category || NULL == group)
+ return FALSE;
+
+ category->groups.push_back(group);
+ return TRUE;
+}
+
+ListWidgetGroup *
+ListWidget_FindGroupEx(ListWidgetCategory *category, const char *name, size_t max)
+{
+ size_t index, count;
+
+ if (NULL == category || NULL == name)
+ return NULL;
+
+ count = category->groups.size();
+ if (max < count)
+ count = max;
+
+ for(index = 0; index < count; index++)
+ {
+ ListWidgetGroup *group = category->groups[index];
+ if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, 0, name, -1, group->name, -1))
+ return group;
+ }
+
+ return NULL;
+}
+
+ListWidgetGroup *
+ListWidget_FindGroup(ListWidgetCategory *category, const char *name)
+{
+ return ListWidget_FindGroupEx(category, name, -1);
+}
+
+static int
+ListWidget_ItemSortCb(const void *element1, const void *element2)
+{
+ ListWidgetItem *item1;
+ ListWidgetItem *item2;
+ int result;
+
+ item1 = (ListWidgetItem*)element1;
+ item2 = (ListWidgetItem*)element2;
+
+ result = CompareString(LOCALE_USER_DEFAULT, 0, item1->title, -1, item2->title, -1);
+ if (CSTR_EQUAL == result || 0 == result)
+ result = CompareStringA(CSTR_INVARIANT, 0, item1->name, -1, item2->name, -1);
+
+ return (result - 2);
+
+}
+static bool
+ListWidget_ItemSortCb_V2(const void* element1, const void* element2)
+{
+ return ListWidget_ItemSortCb(element1, element2) < 0;
+}
+
+void
+ListWidget_SortGroup(ListWidgetGroup *group)
+{
+ if (group->items.size())
+ {
+ //qsort(group->items.first(), group->items.size(), sizeof(ListWidgetItem**), ListWidget_ItemSortCb);
+ std::sort(group->items.begin(), group->items.end(), ListWidget_ItemSortCb_V2);
+ }
+}
diff --git a/Src/Plugins/Library/ml_devices/listWidgetInternal.h b/Src/Plugins/Library/ml_devices/listWidgetInternal.h
new file mode 100644
index 00000000..8c53a729
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/listWidgetInternal.h
@@ -0,0 +1,1020 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_LIST_WIDGET_INTERNAL_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_LIST_WIDGET_INTERNAL_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include <vector>
+#include "./imageCache.h"
+
+typedef enum ListWidgetItemState
+{
+ ListWidgetItemState_Default = (0),
+ ListWidgetItemState_Hovered = (1 << 0),
+ ListWidgetItemState_Selected = (1 << 1),
+ ListWidgetItemState_Interactive = (1 << 2),
+ ListWidgetItemState_TextTruncated = (1 << 3),
+ ListWidgetItemState_TextEdited = (1 << 4),
+
+} ListWidgetItemSate;
+DEFINE_ENUM_FLAG_OPERATORS(ListWidgetItemSate);
+
+#define ListWidgetItem_State(_item) (((ListWidgetItem*)(_item))->state)
+#define ListWidgetItem_SetState(_item, _state) (ListWidgetItem_State(_item) |= (_state))
+#define ListWidgetItem_UnsetState(_item, _state) (ListWidgetItem_State(_item) &= ~(_state))
+
+#define ListWidgetItem_IsHovered(_item) (0 != (ListWidgetItemState_Hovered & ListWidgetItem_State(_item)))
+#define ListWidgetItem_SetHovered(_item) ListWidgetItem_SetState(_item, ListWidgetItemState_Hovered)
+#define ListWidgetItem_UnsetHovered(_item) ListWidgetItem_UnsetState(_item, ListWidgetItemState_Hovered)
+
+#define ListWidgetItem_IsSelected(_item) (0 != (ListWidgetItemState_Selected & ListWidgetItem_State(_item)))
+#define ListWidgetItem_SetSelected(_item) ListWidgetItem_SetState(_item, ListWidgetItemState_Selected)
+#define ListWidgetItem_UnsetSelected(_item) ListWidgetItem_UnsetState(_item, ListWidgetItemState_Selected)
+
+#define ListWidgetItem_IsInteractive(_item) (0 != (ListWidgetItemState_Interactive & ListWidgetItem_State(_item)))
+#define ListWidgetItem_SetInteractive(_item) ListWidgetItem_SetState(_item, ListWidgetItemState_Interactive)
+#define ListWidgetItem_UnsetInteractive(_item) ListWidgetItem_UnsetState(_item, ListWidgetItemState_Interactive)
+
+
+#define ListWidgetItem_IsTextTruncated(_item) (0 != (ListWidgetItemState_TextTruncated & ListWidgetItem_State(_item)))
+#define ListWidgetItem_SetTextTruncated(_item) ListWidgetItem_SetState(_item, ListWidgetItemState_TextTruncated)
+#define ListWidgetItem_UnsetTextTruncated(_item) ListWidgetItem_UnsetState(_item, ListWidgetItemState_TextTruncated)
+
+#define ListWidgetItem_IsTextEdited(_item) (0 != (ListWidgetItemState_TextEdited & ListWidgetItem_State(_item)))
+#define ListWidgetItem_SetTextEdited(_item) ListWidgetItem_SetState(_item, ListWidgetItemState_TextEdited)
+#define ListWidgetItem_UnsetTextEdited(_item) ListWidgetItem_UnsetState(_item, ListWidgetItemState_TextEdited)
+
+
+typedef struct ListWidgetConnection ListWidgetConnection;
+typedef std::vector<ListWidgetConnection*> ListWidgetConnectionList;
+
+typedef struct ListWidgetTooltip ListWidgetTooltip;
+
+typedef enum ListWidgetCommandState
+{
+ ListWidgetCommandState_Normal = (0),
+ ListWidgetCommandState_Disabled = (1 << 0),
+ ListWidgetCommandState_Primary = (1 << 1),
+ ListWidgetCommandState_Pressed = (1 << 2),
+} ListWidgetCommandState;
+DEFINE_ENUM_FLAG_OPERATORS(ListWidgetCommandState);
+
+typedef struct ListWidgetCommand ListWidgetCommand;
+
+typedef struct ListWidgetActivity
+{
+ unsigned int step;
+ unsigned int percent;
+ wchar_t *title;
+ SIZE titleSize;
+ BOOL cancelable;
+}
+ListWidgetActivity;
+
+typedef struct ListWidgetActivityMetric
+{
+ long height;
+ long width;
+ long progressWidth;
+ long progressHeight;
+ long percentWidth;
+ long percentHeight;
+ long titleWidth;
+ long titleHeight;
+ long fontHeight;
+ long offsetLeft;
+ long offsetRight;
+ long offsetTop;
+ long offsetBottom;
+ long spacing;
+} ListWidgetActivityMetric;
+
+typedef enum ListWidgetActivityChange
+{
+ ListWidgetActivityChanged_Nothing = 0,
+ ListWidgetActivityChanged_Percent = (1 << 0),
+ ListWidgetActivityChanged_Title = (1 << 1),
+ ListWidgetActivityChanged_Cancelable = (1 << 2),
+ ListWidgetActivityChanged_All = (ListWidgetActivityChanged_Percent | ListWidgetActivityChanged_Title | ListWidgetActivityChanged_Cancelable ),
+}
+ListWidtetActivityChange;
+DEFINE_ENUM_FLAG_OPERATORS(ListWidtetActivityChange);
+
+
+typedef enum ListWidgetItemPart
+{
+ ListWidgetItemPart_None = 0,
+ ListWidgetItemPart_Frame = (1 << 0),
+ ListWidgetItemPart_Image = (1 << 1),
+ ListWidgetItemPart_Title = (1 << 2),
+ ListWidgetItemPart_Activity = (1 << 3),
+ ListWidgetItemPart_Command = (1 << 4),
+ ListWidgetItemPart_Spacebar = (1 << 5),
+ ListWidgetItemPart_Connection = (1 << 6),
+}ListWidgetItemPart;
+DEFINE_ENUM_FLAG_OPERATORS(ListWidgetItemPart);
+
+typedef struct ListWidgetItem
+{
+ char *name;
+ wchar_t *title;
+ RECT rect;
+ SIZE titleSize;
+ DeviceImage *image;
+ uint64_t spaceTotal;
+ uint64_t spaceUsed;
+ ListWidgetItemState state;
+ ListWidgetConnection *connection;
+ ListWidgetActivity *activity;
+}ListWidgetItem;
+typedef std::vector<ListWidgetItem*> ListWidgetItemList;
+
+typedef struct ListWidgetGroup
+{
+ char *name;
+ wchar_t *title;
+ ListWidgetItemList items;
+} ListWidgetGroup;
+typedef std::vector<ListWidgetGroup*> ListWidgetGroupList;
+
+typedef struct ListWidgetCategory
+{
+ char *name;
+ wchar_t *title;
+ BOOL collapsed;
+ ListWidgetGroupList groups;
+ RECT rect;
+ long titleWidth;
+ long countWidth;
+ wchar_t *countString;
+ wchar_t *emptyText;
+ RECT emptyTextRect;
+}ListWidgetCategory;
+
+typedef std::vector<ListWidgetCategory*> ListWidgetCategoryList;
+
+typedef enum ListWidgetFlags
+{
+ ListWidgetFlag_NoFocusSelect = (1 << 0),
+ ListWidgetFlag_LButtonDownOnCommand = (1 << 1),
+} ListWidgetFlags;
+DEFINE_ENUM_FLAG_OPERATORS(ListWidgetFlags);
+
+typedef struct ListWidget
+{
+ ListWidgetFlags flags;
+ ListWidgetCategoryList categories;
+ ListWidgetConnectionList connections;
+ BackBuffer backBuffer;
+ ListWidgetItem *hoveredItem;
+ ListWidgetItem *selectedItem;
+ ListWidgetItem *titleEditItem;
+ ListWidgetCategory *pressedCategory;
+ SIZE imageSize;
+ long itemWidth;
+ size_t itemsPerLine;
+ size_t deviceHandler;
+ ListWidgetCommand **commands;
+ size_t commandsCount;
+ size_t commandsMax;
+ ListWidgetItemList activeItems;
+ POINT previousMouse;
+
+ HBITMAP spacebarBitmap;
+ HBITMAP arrowsBitmap;
+
+ HBITMAP hoverBitmap;
+ HBITMAP selectBitmap;
+ HBITMAP inactiveSelectBitmap;
+
+ HBITMAP largeBadgeBitmap;
+ HBITMAP smallBadgeBitmap;
+
+ SIZE connectionSize;
+ SIZE primaryCommandSize;
+ SIZE secondaryCommandSize;
+ DeviceImage *unknownCommandLargeImage;
+ DeviceImage *unknownCommandSmallImage;
+
+ ListWidgetActivityMetric activityMetrics;
+ HFONT activityFont;
+ HBITMAP activityBadgeBitmap;
+ DeviceImage *activityProgressImage;
+ BOOL activityTimerEnabled;
+
+ HMENU activeMenu;
+ ListWidgetTooltip *tooltip;
+
+ unsigned int selectionStatus;
+ HWND titleEditor;
+
+} ListWidget;
+
+typedef struct
+ListWidgetItemMetric
+{
+ long titleMinWidth;
+ long offsetLeft;
+ long offsetTop;
+ long offsetRight;
+ long offsetBottom;
+ long imageOffsetLeft;
+ long imageOffsetTop;
+ long imageOffsetRight;
+ long imageOffsetBottom;
+ long titleOffsetTop;
+ long spacebarOffsetTop;
+ long spacebarHeight;
+} ListWidgetItemMetric;
+
+typedef struct
+ListWidgetCategoryMetric
+{
+ long offsetLeft;
+ long offsetTop;
+ long offsetRight;
+ long offsetBottom;
+ long lineHeight;
+ long lineOffsetTop;
+ long titleOffsetLeft;
+ long minHeight;
+ long iconWidth;
+ long iconHeight;
+} ListWidgetCategoryMetric;
+
+HBITMAP
+ListWidget_GetSpacebarBitmap(ListWidget *self,
+ WidgetStyle *style,
+ HWND hwnd,
+ long width,
+ long height);
+
+HBITMAP
+ListWidget_GetHoverBitmap(ListWidget *self,
+ WidgetStyle *style,
+ HWND hwnd,
+ long width,
+ long height);
+
+HBITMAP
+ListWidget_GetSelectBitmap(ListWidget *self,
+ WidgetStyle *style,
+ HWND hwnd,
+ long width,
+ long height);
+
+HBITMAP
+ListWidget_GetInactiveSelectBitmap(ListWidget *self,
+ WidgetStyle *style,
+ HWND hwnd,
+ long width,
+ long height);
+
+HBITMAP
+ListWidget_GetLargeBadgeBitmap(ListWidget *self,
+ WidgetStyle *style,
+ HWND hwnd,
+ long width,
+ long height);
+
+
+HBITMAP
+ListWidget_GetSmallBadgeBitmap(ListWidget *self,
+ WidgetStyle *style,
+ HWND hwnd,
+ long width,
+ long height);
+
+HBITMAP
+ListWidget_GetUnknownCommandSmallBitmap(ListWidget *self,
+ WidgetStyle *style,
+ long width,
+ long height);
+
+HBITMAP
+ListWidget_GetUnknownCommandLargeBitmap(ListWidget *self,
+ WidgetStyle *style,
+ long width,
+ long height);
+
+HBITMAP
+ListWidget_GetArrowsBitmap(ListWidget *self,
+ WidgetStyle *style,
+ HWND hwnd);
+
+HBITMAP
+ListWidget_GetActivityProgressBitmap(ListWidget *self,
+ WidgetStyle *style);
+
+
+HBITMAP
+ListWidget_GetActivityBadgeBitmap(ListWidget *self,
+ WidgetStyle *style,
+ HWND hwnd,
+ long width,
+ long height);
+
+BOOL
+ListWidget_GetViewOrigin(HWND hwnd,
+ POINT *pt);
+
+BOOL
+ListWidget_UpdateHoverEx(ListWidget *self,
+ HWND hwnd,
+ const POINT *cursor);
+
+BOOL
+ListWidget_UpdateHover(ListWidget *self,
+ HWND hwnd);
+
+BOOL
+ListWidget_RemoveHover(ListWidget *self,
+ HWND hwnd,
+ BOOL invalidate);
+
+BOOL
+ListWidget_SelectItem(ListWidget *self,
+ HWND hwnd,
+ ListWidgetItem *item,
+ BOOL ensureVisible);
+
+BOOL
+ListWidget_SetImageSize(ListWidget *self,
+ HWND hwnd,
+ int imageWidth,
+ int imageHeight,
+ BOOL redraw);
+
+typedef enum ListWidgetLayoutFlags
+{
+ ListWidgetLayout_Normal = 0,
+ ListWidgetLayout_NoRedraw = (1 << 0),
+ ListWidgetLayout_UpdateNow = (1 << 1),
+ ListWidgetLayout_KeepStable = (1 << 2),
+}ListWidgetLayoutFlags;
+DEFINE_ENUM_FLAG_OPERATORS(ListWidgetLayoutFlags);
+
+BOOL
+ListWidget_UpdateLayout(HWND hwnd,
+ ListWidgetLayoutFlags flags);
+
+BOOL
+ListWidget_DisplayContextMenu(ListWidget *self,
+ HWND hostWindow,
+ POINT pt);
+
+BOOL
+ListWidget_RegisterActiveItem(ListWidget *self,
+ HWND hwnd,
+ ListWidgetItem *item);
+
+BOOL
+ListWidget_UnregisterActiveItem(ListWidget *self,
+ HWND hwnd,
+ ListWidgetItem *item);
+
+double
+ListWidget_GetZoomRatio(ListWidget *self);
+
+void
+ListWidget_UpdateSelectionStatus(ListWidget *self,
+ HWND hwnd,
+ BOOL ensureVisible);
+
+void
+ListWidget_UpdateSelectionSpaceStatus(ListWidget *self,
+ HWND hwnd,
+ BOOL ensureVisible);
+
+void
+ListWidget_UpdateTitleEditorColors(HWND editor,
+ WidgetStyle *style);
+/*
+<<<<<<<<<<<<<<<<<<<<<<<<< Category >>>>>>>>>>>>>>>>>>>>>>>>>
+*/
+
+ListWidgetCategory *
+ListWidget_CreateCategory(const char *name,
+ const wchar_t *title,
+ BOOL collapsed);
+
+void
+ListWidget_DestroyCategory(ListWidgetCategory *category);
+
+ListWidgetCategory *
+ListWidget_GetCategoryFromPoint(ListWidget *self,
+ POINT point);
+
+ListWidgetCategory *
+ListWidget_FindCategory(ListWidget *self,
+ const char *name);
+
+BOOL
+ListWidget_GetCategoryMetrics(WidgetStyle *style,
+ ListWidgetCategoryMetric *metrics);
+
+BOOL
+ListWidget_ToggleCategory(ListWidgetCategory *category,
+ HWND hwnd);
+
+void
+ListWidget_ResetCategoryCounter(ListWidgetCategory *category);
+
+void
+ListWidget_SortCategory(ListWidgetCategory *category);
+
+BOOL
+ListWidget_SetCategoryEmptyText(ListWidgetCategory *category, const wchar_t *text);
+
+/*
+<<<<<<<<<<<<<<<<<<<<<<<<< Group >>>>>>>>>>>>>>>>>>>>>>>>>
+*/
+
+ListWidgetGroup *
+ListWidget_CreateGroup(const char *name);
+
+ListWidgetGroup *
+ListWidget_CreateGroupEx(const char *name,
+ const wchar_t *title);
+
+void
+ListWidget_DestroyGroup(ListWidgetGroup *group);
+
+BOOL
+ListWidget_AddGroup(ListWidgetCategory *category,
+ ListWidgetGroup *group);
+
+
+ListWidgetGroup *
+ListWidget_FindGroup(ListWidgetCategory *category,
+ const char *name);
+
+ListWidgetGroup *
+ListWidget_FindGroupEx(ListWidgetCategory *category,
+ const char *name,
+ size_t max);
+
+void
+ListWidget_SortGroup(ListWidgetGroup *group);
+
+/*
+<<<<<<<<<<<<<<<<<<<<<<<<< Item >>>>>>>>>>>>>>>>>>>>>>>>>
+*/
+
+typedef enum ListWidgetVisibleFlags
+{
+ VISIBLE_NORMAL = 0,
+ VISIBLE_PARTIAL_OK = (1 << 0),
+ VISIBLE_ALIGN_BOTTOM =(1 << 1),
+ VISIBLE_ALIGN_TOP = (1 << 2),
+ VISIBLE_ALIGN_ALWAYS = (1 << 3),
+} ListWidgetVisibleFlags;
+DEFINE_ENUM_FLAG_OPERATORS(ListWidgetVisibleFlags);
+
+ListWidgetItem*
+ListWidget_CreateItemFromDevice(ListWidget *self,
+ ifc_device* device);
+
+void
+ListWidget_DestroyItem(ListWidgetItem *item);
+
+BOOL
+ListWidget_CalculateItemBaseSize(ListWidget *self,
+ WidgetStyle *style,
+ SIZE *baseSize,
+ long *itemTextWidth);
+
+size_t // number of removed items
+ListWidget_RemoveItem(ListWidget *self,
+ HWND hwnd,
+ const char *name);
+
+ListWidgetItem *
+ListWidget_GetFirstItem(ListWidget *self);
+
+ListWidgetItem *
+ListWidget_GetLastItem(ListWidget *self);
+
+ListWidgetItem *
+ListWidget_GetNextItem(ListWidget *self,
+ ListWidgetItem *baseItem);
+
+ListWidgetItem *
+ListWidget_GetPreviousItem(ListWidget *self,
+ ListWidgetItem *baseItem);
+
+ListWidgetItem *
+ListWidget_GetNextCategoryItem(ListWidget *self,
+ ListWidgetCategory *category,
+ ListWidgetItem *baseItem);
+
+ListWidgetItem *
+ListWidget_GetPreviousCategoryItem(ListWidget *self,
+ ListWidgetCategory *category,
+ ListWidgetItem *baseItem);
+
+ListWidgetItem *
+ListWidget_GetNextGroupItem(ListWidget *self,
+ ListWidgetGroup *group,
+ ListWidgetItem *baseItem);
+
+ListWidgetItem *
+ListWidget_GetPreviousGroupItem(ListWidget *self,
+ ListWidgetGroup *group,
+ ListWidgetItem *baseItem);
+
+ListWidgetItem *
+ListWidget_GetNextLineItem(ListWidget *self,
+ ListWidgetItem *baseItem);
+
+ListWidgetItem *
+ListWidget_GetPreviousLineItem(ListWidget *self,
+ ListWidgetItem *baseItem);
+
+ListWidgetItem *
+ListWidget_GetNextPageItem(ListWidget *self,
+ HWND hwnd,
+ ListWidgetItem *baseItem);
+
+ListWidgetItem *
+ListWidget_GetPreviousPageItem(ListWidget *self,
+ HWND hwnd,
+ ListWidgetItem *baseItem);
+
+BOOL
+ListWidget_EnsureItemVisisble(ListWidget *self,
+ HWND hwnd,
+ ListWidgetItem *item,
+ ListWidgetVisibleFlags flags);
+
+HBITMAP
+ListWidget_GetItemImage(ListWidget *self,
+ WidgetStyle *style,
+ ListWidgetItem *item);
+
+BOOL
+ListWidget_GetItemMetrics(WidgetStyle *style,
+ ListWidgetItemMetric *metrics);
+
+ListWidgetItem *
+ListWidget_GetItemFromPointEx(ListWidget *self,
+ POINT point,
+ ListWidgetCategory **categoryOut, // optional
+ ListWidgetGroup **groupOut); // optional
+
+ListWidgetItem *
+ListWidget_GetItemFromPoint(ListWidget *self,
+ POINT point);
+
+
+
+BOOL
+ListWidget_AddItem(ListWidgetGroup *group,
+ ListWidgetItem *item);
+
+ListWidgetItem *
+ListWidget_FindGroupItem(ListWidgetGroup *group,
+ const char *name);
+
+ListWidgetItem *
+ListWidget_FindGroupItemEx(ListWidgetGroup *group,
+ const char *name,
+ size_t max);
+
+ListWidgetGroup *
+ListWidget_GetItemOwner(ListWidget *self,
+ ListWidgetItem *baseItem,
+ ListWidgetCategory **categoryOut);
+
+
+ListWidgetItem *
+ListWidget_FindItem(ListWidget *self,
+ const char *name,
+ ListWidgetCategory **categoryOut,
+ ListWidgetGroup **groupOut);
+
+BOOL
+ListWidget_FindItemPos(ListWidget *self,
+ ListWidgetItem *item,
+ size_t *categoryOut,
+ size_t *groupOut,
+ size_t *itemOut);
+
+
+BOOL
+ListWidget_SetItemTitle(ListWidgetItem *item,
+ const wchar_t *title);
+
+BOOL
+ListWidget_DisplayItemContextMenu(ListWidget *self,
+ HWND hostWindow,
+ ListWidgetItem *item,
+ POINT pt);
+
+size_t
+ListWidget_GetItemCommands(ListWidgetItem *item,
+ ListWidgetCommand **buffer,
+ size_t bufferMax);
+
+BOOL
+ListWidget_SendItemCommand(const char *name,
+ const char *command,
+ HWND hostWindow,
+ ULONG_PTR param,
+ BOOL enableIntercept);
+
+BOOL
+ListWidget_CreateItemActivity(ListWidgetItem *item);
+
+BOOL
+ListWidget_DeleteItemActivity(ListWidgetItem *item);
+
+
+
+ListWidtetActivityChange
+ListWidget_UpdateItemActivity(ListWidgetItem *item,
+ ifc_deviceactivity *activity);
+
+BOOL
+ListWidget_InvalidateItemImage(ListWidget *self,
+ HWND hwnd,
+ ListWidgetItem *item);
+
+BOOL
+ListWidget_InvalidateItemActivity(ListWidget *self,
+ HWND hwnd,
+ ListWidgetItem *item,
+ ListWidgetActivityChange changes);
+
+
+BOOL
+ListWidget_GetItemFrameRect(ListWidget *self,
+ ListWidgetItem *item,
+ ListWidgetItemMetric *metrics,
+ RECT *rect);
+
+BOOL
+ListWidget_GetItemImageRect(ListWidget *self,
+ ListWidgetItem *item,
+ ListWidgetItemMetric *metrics,
+ RECT *rect);
+
+BOOL
+ListWidget_GetItemActivityRect(ListWidget *self,
+ ListWidgetItem *item,
+ ListWidgetItemMetric *metrics,
+ RECT *rect);
+
+BOOL
+ListWidget_GetItemActivityProgressRect(ListWidget *self,
+ HDC hdc,
+ ListWidgetItem *item,
+ ListWidgetItemMetric *metrics,
+ RECT *rect);
+
+BOOL
+ListWidget_GetItemActivityPercentRect(ListWidget *self,
+ HDC hdc,
+ ListWidgetItem *item,
+ ListWidgetItemMetric *metrics,
+ RECT *rect);
+
+BOOL
+ListWidget_GetItemActivityTitleRect(ListWidget *self,
+ HDC hdc,
+ ListWidgetItem *item,
+ ListWidgetItemMetric *metrics,
+ RECT *rect);
+
+BOOL
+ListWidget_GetItemSpacebarRect(ListWidget *self,
+ ListWidgetItem *item,
+ ListWidgetItemMetric *metrics,
+ RECT *rect);
+
+BOOL
+ListWidget_GetItemTitleRect(ListWidget *self,
+ ListWidgetItem *item,
+ ListWidgetItemMetric *metrics,
+ BOOL exactSize,
+ RECT *rect);
+
+BOOL
+ListWidget_GetItemConnectionRect(ListWidget *self,
+ ListWidgetItem *item,
+ ListWidgetItemMetric *metrics,
+ RECT *rect);
+
+ListWidgetItemPart
+ListWidget_GetItemPartFromPoint(ListWidget *self,
+ ListWidgetItem *item,
+ ListWidgetItemMetric *metrics,
+ POINT pt,
+ ListWidgetItemPart mask,
+ RECT *partRect);
+
+BOOL
+ListWidget_FormatItemTip(ListWidget *self,
+ ListWidgetItem *item,
+ wchar_t *buffer,
+ size_t bufferMax);
+
+BOOL
+ListWidget_FormatItemTitleTip(ListWidget *self,
+ ListWidgetItem *item,
+ wchar_t *buffer,
+ size_t bufferMax);
+
+BOOL
+ListWidget_FormatItemCommandTip(ListWidget *self,
+ ListWidgetItem *item,
+ const RECT *commandRect,
+ wchar_t *buffer,
+ size_t bufferMax);
+
+BOOL
+ListWidget_FormatItemSpaceTip(ListWidget *self,
+ ListWidgetItem *item,
+ wchar_t *buffer,
+ size_t bufferMax);
+
+BOOL
+ListWidget_FormatItemStatus(ListWidget *self,
+ ListWidgetItem *item,
+ wchar_t *buffer,
+ size_t bufferMax);
+
+BOOL
+ListWidget_FormatItemSpaceStatus(ListWidget *self,
+ ListWidgetItem *item,
+ wchar_t *buffer,
+ size_t bufferMax);
+HWND
+ListWidget_BeginItemTitleEdit(ListWidget *self,
+ HWND hwnd,
+ ListWidgetItem *item);
+
+int
+ListWidget_CompareItemPos(ListWidget *self,
+ ListWidgetItem *item1,
+ ListWidgetItem *item2);
+
+BOOL
+ListWidget_GetViewItemPos(HWND hwnd,
+ ListWidgetItem *item,
+ POINT *pt);
+
+/*
+<<<<<<<<<<<<<<<<<<<<<<<<< Connection >>>>>>>>>>>>>>>>>>>>>>>>>
+*/
+
+ListWidgetConnection *
+ListWidget_CreateConnection(const char *name);
+
+void
+ListWidget_DestroyConnection(ListWidgetConnection *connection);
+
+
+HBITMAP
+ListWidget_GetConnectionImage(WidgetStyle *style,
+ ListWidgetConnection *connection,
+ int width,
+ int height);
+
+BOOL
+ListWidget_ConnectionResetColors(WidgetStyle *style,
+ ListWidgetConnection *connection);
+
+void
+ListWidget_ResetConnnectionsColors(ListWidget *self,
+ WidgetStyle *style);
+
+ListWidgetConnection *
+ListWidget_FindConnection(ListWidget *self,
+ const char *name);
+
+BOOL
+ListWidget_AddConnection(ListWidget *self,
+ ListWidgetConnection *connection);
+
+void
+ListWidget_RemoveConnection(ListWidget *self,
+ const char *name);
+
+void
+ListWidget_RemoveAllConnections(ListWidget *self);
+
+
+BOOL
+ListWidget_UpdateConnectionImageSize(ListWidgetConnection *connection,
+ int width,
+ int height);
+
+/*
+<<<<<<<<<<<<<<<<<<<<<<<<< Command >>>>>>>>>>>>>>>>>>>>>>>>>
+*/
+
+ListWidgetCommand *
+ListWidget_CreateCommand(const char *name,
+ BOOL primary,
+ BOOL disabled);
+
+void
+ListWidget_DestroyCommand(ListWidgetCommand *command);
+
+size_t
+ListWigdet_GetDeviceCommands(ListWidgetCommand **buffer,
+ size_t bufferMax,
+ ifc_device *device);
+
+void
+ListWidget_DestroyAllCommands(ListWidgetCommand** buffer,
+ size_t bufferMax);
+
+const wchar_t *
+ListWidget_GetCommandTitle(ListWidgetCommand *command);
+
+const wchar_t *
+ListWidget_GetCommandDescription(ListWidgetCommand *command);
+
+HBITMAP
+ListWidget_GetCommandLargeBitmap(WidgetStyle *style,
+ ListWidgetCommand *command,
+ int width,
+ int height);
+
+HBITMAP
+ListWidget_GetCommandSmallBitmap(WidgetStyle *style,
+ ListWidgetCommand *command,
+ int width,
+ int height);
+
+BOOL
+ListWidget_ResetCommandImages(ListWidgetCommand *command);
+
+BOOL
+ListWidget_GetCommandRect(ListWidgetCommand *command,
+ RECT *rect);
+
+BOOL
+ListWidget_SetCommandRect(ListWidgetCommand *command,
+ const RECT *rect);
+
+BOOL
+ListWidget_GetCommandRectEqual(ListWidgetCommand *command,
+ const RECT *rect);
+
+BOOL
+ListWidget_GetCommandPrimary(ListWidgetCommand *command);
+
+BOOL
+ListWidget_GetCommandDisabled(ListWidgetCommand *command);
+
+
+BOOL
+ListWidget_EnableCommand(ListWidgetCommand *command,
+ BOOL enable);
+
+BOOL
+ListWidget_GetCommandPressed(ListWidgetCommand *command);
+
+
+BOOL
+ListWidget_SetCommandPressed(ListWidgetCommand *command,
+ BOOL pressed);
+
+const char *
+ListWidget_GetCommandName(ListWidgetCommand *command);
+
+/*
+<<<<<<<<<<<<<<<<<<<<<<<<< Paint >>>>>>>>>>>>>>>>>>>>>>>>>
+*/
+
+typedef struct ListWidgetPaintSpacebar
+{
+ HBITMAP bitmap;
+ long width;
+ long height;
+ long emptyBarOffset;
+ long filledBarOffset;
+} ListWidgetPaintSpacebar;
+
+typedef struct ListWidgetPaintArrow
+{
+ HBITMAP bitmap;
+ long width;
+ long height;
+ long collapsedOffset;
+ long expandedOffset;
+} ListWidgetPaintArrow;
+
+
+typedef struct ListWidgetPaint
+{
+ ListWidget *widget;
+ WidgetStyle *style;
+ HWND hwnd;
+ HDC hdc;
+ BOOL erase;
+ const RECT *paintRect;
+ HDC sourceDC;
+ ListWidgetPaintSpacebar spacebar;
+ ListWidgetItemMetric itemMetrics;
+ ListWidgetCategoryMetric categoryMetrics;
+ ListWidgetPaintArrow arrow;
+ RECT partRect;
+ BOOL focused;
+} ListWidgetPaint;
+
+BOOL
+ListWidgetPaint_Initialize(ListWidgetPaint *self,
+ ListWidget *widget,
+ WidgetStyle *style,
+ HWND hwnd,
+ HDC hdc,
+ const RECT *paintRect,
+ BOOL erase);
+
+void
+ListWidgetPaint_Uninitialize(ListWidgetPaint *self);
+
+BOOL
+ListWidgetPaint_DrawItem(ListWidgetPaint *self,
+ ListWidgetItem *item);
+
+BOOL
+ListWidgetPaint_DrawCategory(ListWidgetPaint *self,
+ ListWidgetCategory *category);
+
+BOOL
+ListWidgetPaint_DrawEmptyCategoryText(ListWidgetPaint *self,
+ ListWidgetCategory *category);
+
+
+/*
+<<<<<<<<<<<<<<<<<<<<<<<<< Tooltip >>>>>>>>>>>>>>>>>>>>>>>>>
+*/
+
+ListWidgetTooltip*
+ListWidget_TooltipCreate(HWND hwnd);
+
+void
+ListWidget_TooltipDestroy(ListWidgetTooltip *tooltip);
+
+void
+ListWidget_TooltipFontChanged(ListWidgetTooltip *tooltip);
+
+BOOL
+ListWidget_TooltipActivate(ListWidgetTooltip *tooltip,
+ const RECT *rect);
+
+BOOL
+ListWidget_TooltipUpdate(ListWidgetTooltip *tooltip,
+ ListWidgetItem *item,
+ ListWidgetItemPart part,
+ const RECT *partRect);
+
+void
+ListWidget_TooltipHide(ListWidgetTooltip *tooltip);
+
+void
+ListWidget_TooltipRelayMouseMessage(ListWidgetTooltip *tooltip,
+ unsigned int message,
+ unsigned int vKeys,
+ const POINT *cursor);
+BOOL
+ListWidget_TooltipProcessNotification(ListWidget *self,
+ ListWidgetTooltip *tooltip,
+ NMHDR *pnmh,
+ LRESULT *result);
+
+ListWidgetItem *
+ListWidget_TooltipGetCurrent(ListWidgetTooltip *tooltip,
+ ListWidgetItemPart *part,
+ RECT *partRect);
+
+BOOL
+ListWidget_TooltipGetChanged(ListWidgetTooltip *tooltip,
+ ListWidgetItem *item,
+ ListWidgetItemPart part,
+ const RECT *partRect);
+
+typedef enum TooltipUpdateReason
+{
+ Tooltip_DeviceTitleChanged = 1,
+ Tooltip_DeviceSpaceChanged = 2,
+ Tooltip_DeviceActivityChanged = 3,
+ Tooltip_DeviceModelChanged = 4,
+ Tooltip_DeviceStatusChanged = 5,
+} TooltipUpdateReason;
+
+BOOL
+ListWidget_TooltipUpdateText(ListWidget *self,
+ ListWidgetTooltip *tooltip,
+ ListWidgetItem *item,
+ TooltipUpdateReason reason);
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_LIST_WIDGET_INTERNAL_HEADER
+
diff --git a/Src/Plugins/Library/ml_devices/listWidgetItem.cpp b/Src/Plugins/Library/ml_devices/listWidgetItem.cpp
new file mode 100644
index 00000000..d10ad9c6
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/listWidgetItem.cpp
@@ -0,0 +1,2854 @@
+#include "main.h"
+#include "./listWidgetInternal.h"
+#include "../nu/AutoWide.h"
+#include <strsafe.h>
+
+#define LISTWIDGETITEM_OFFSET_LEFT_DLU 0
+#define LISTWIDGETITEM_OFFSET_TOP_DLU 0
+#define LISTWIDGETITEM_OFFSET_RIGHT_DLU 0
+#define LISTWIDGETITEM_OFFSET_BOTTOM_DLU 2
+#define LISTWIDGETITEM_IMAGE_OFFSET_LEFT_DLU 2
+#define LISTWIDGETITEM_IMAGE_OFFSET_TOP_DLU 2
+#define LISTWIDGETITEM_IMAGE_OFFSET_RIGHT_DLU 2
+#define LISTWIDGETITEM_IMAGE_OFFSET_BOTTOM_DLU 2
+#define LISTWIDGETITEM_SPACEBAR_OFFSET_DLU 1
+#define LISTWIDGETITEM_SPACEBAR_HEIGHT_DLU 8
+#define LISTWIDGETITEM_TITLE_OFFSET_DLU 1
+#define LISTWIDGETITEM_TITLE_MIN_WIDTH_DLU (8 * 4)
+#define LISTWIDGETITEM_TITLE_EDITOR_MARGIN_HORZ_DLU 2
+
+ListWidgetItem*
+ListWidget_CreateItemFromDevice(ListWidget *self, ifc_device* device)
+{
+ ListWidgetItem *item;
+ ifc_deviceactivity *activity;
+ wchar_t buffer[1024] = {0};
+
+ if (NULL == device || NULL == device->GetName())
+ return NULL;
+
+ item = new ListWidgetItem();
+ if (NULL == item)
+ return NULL;
+
+ item->name = AnsiString_Duplicate(device->GetName());
+ if (NULL == item->name)
+ {
+ delete item;
+ return NULL;
+ }
+
+ if (SUCCEEDED(device->GetDisplayName(buffer, ARRAYSIZE(buffer))))
+ item->title = String_Duplicate(buffer);
+ else
+ item->title = NULL;
+
+ ListWidgetItem_UnsetTextTruncated(item);
+ SetSize(&item->titleSize, -1, -1);
+
+ item->image = NULL;
+
+ if (FAILED(device->GetTotalSpace(&item->spaceTotal)))
+ item->spaceTotal = 0;
+
+ if (FAILED(device->GetUsedSpace(&item->spaceUsed)))
+ item->spaceUsed = 0;
+
+ item->connection = NULL;
+ if (NULL != self)
+ {
+ item->connection = ListWidget_FindConnection(self, device->GetConnection());
+ if (NULL == item->connection)
+ {
+ item->connection = ListWidget_CreateConnection(device->GetConnection());
+ if (NULL != item->connection)
+ ListWidget_AddConnection(self, item->connection);
+ }
+ }
+
+
+
+ item->activity = NULL;
+ if (S_OK == device->GetActivity(&activity) && NULL != activity)
+ {
+ if (FALSE != activity->GetActive())
+ {
+ ListWidget_CreateItemActivity(item);
+ ListWidget_UpdateItemActivity(item, activity);
+ }
+
+ activity->Release();
+ }
+ return item;
+}
+
+void
+ListWidget_DestroyItem(ListWidgetItem *item)
+{
+ if (NULL == item)
+ return;
+
+ if (NULL != item->image)
+ DeviceImage_Release(item->image);
+
+ ListWidget_DeleteItemActivity(item);
+
+ AnsiString_Free(item->name);
+ String_Free(item->title);
+
+
+
+ delete item;
+}
+
+BOOL
+ListWidget_SetItemTitle(ListWidgetItem *item, const wchar_t *title)
+{
+ if (NULL == item)
+ return FALSE;
+
+ String_Free(item->title);
+ SetSize(&item->titleSize, -1, -1);
+ ListWidgetItem_UnsetTextTruncated(item);
+
+ item->title = String_Duplicate(title);
+
+ if (NULL != title && NULL == item->title)
+ return FALSE;
+
+ return TRUE;
+
+}
+
+size_t
+ListWidget_RemoveItem(ListWidget *self, HWND hwnd, const char *name)
+{
+ size_t iCategory, iItem;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+ size_t removed;
+ RECT rect;
+ POINT origin;
+ ListWidgetItem *selectItem;
+
+ removed = 0;
+ selectItem = NULL;
+
+ if (NULL == self || NULL == name)
+ return 0;
+
+ if (FALSE == ListWidget_GetViewOrigin(hwnd, &origin))
+ ZeroMemory(&origin, sizeof(POINT));
+
+ for (iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ category = self->categories[iCategory];
+ BOOL categoryModified = FALSE;
+
+ size_t iGroup = category->groups.size();
+ while(iGroup--)
+ {
+ group = category->groups[iGroup];
+ iItem = group->items.size();
+ while(iItem--)
+ {
+ item = group->items[iItem];
+ if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, 0, item->name, -1, name, -1))
+ {
+ removed++;
+ categoryModified = TRUE;
+
+ if (NULL != item->activity)
+ ListWidget_UnregisterActiveItem(self, hwnd, item);
+
+ if (self->selectedItem == item)
+ {
+ self->selectedItem = NULL;
+
+ if (NULL != self->activeMenu)
+ EndMenu();
+
+ ListWidget_UpdateSelectionStatus(self, hwnd, FALSE);
+
+ selectItem = ListWidget_GetNextGroupItem(self, group, item);
+ if (NULL == selectItem)
+ {
+ selectItem = ListWidget_GetPreviousGroupItem(self, group, item);
+ if (NULL == selectItem)
+ {
+ selectItem = ListWidget_GetNextCategoryItem(self, category, item);
+ if (NULL == selectItem)
+ selectItem = ListWidget_GetPreviousCategoryItem(self, category, item);
+ }
+ }
+ }
+
+ if (self->hoveredItem == item)
+ self->hoveredItem = NULL;
+
+ group->items.erase(group->items.begin() + iItem);
+ ListWidget_DestroyItem(item);
+
+ if (0 == group->items.size())
+ {
+ category->groups.erase(category->groups.begin() + iGroup);
+ ListWidget_DestroyGroup(group);
+ break;
+ }
+ }
+ }
+ }
+
+ if (FALSE != categoryModified)
+ {
+ ListWidget_ResetCategoryCounter(category);
+
+ if (FALSE == category->collapsed)
+ {
+ ListWidget_UpdateLayout(hwnd, ListWidgetLayout_UpdateNow | ListWidgetLayout_KeepStable);
+ }
+ else
+ {
+ CopyRect(&rect, &category->rect);
+ OffsetRect(&rect, origin.x, origin.y);
+ InvalidateRect(hwnd, &rect, FALSE);
+ }
+ }
+ }
+
+ if (0 != removed && NULL != selectItem)
+ ListWidget_SelectItem(self, hwnd, selectItem, FALSE);
+
+ return removed;
+}
+
+BOOL
+ListWidget_GetItemMetrics(WidgetStyle *style, ListWidgetItemMetric *metrics)
+{
+ if (NULL == metrics || NULL == style)
+ return FALSE;
+
+ WIDGETSTYLE_DLU_TO_HORZ_PX_MIN(metrics->offsetLeft, style, LISTWIDGETITEM_OFFSET_LEFT_DLU, 1);
+ WIDGETSTYLE_DLU_TO_VERT_PX_MIN(metrics->offsetTop, style, LISTWIDGETITEM_OFFSET_TOP_DLU, 1);
+ WIDGETSTYLE_DLU_TO_HORZ_PX_MIN(metrics->offsetRight, style, LISTWIDGETITEM_OFFSET_RIGHT_DLU, 1);
+ WIDGETSTYLE_DLU_TO_VERT_PX_MIN(metrics->offsetBottom, style, LISTWIDGETITEM_OFFSET_BOTTOM_DLU, 1);
+
+ WIDGETSTYLE_DLU_TO_HORZ_PX_MIN(metrics->imageOffsetLeft, style, LISTWIDGETITEM_IMAGE_OFFSET_LEFT_DLU, 2);
+ WIDGETSTYLE_DLU_TO_VERT_PX_MIN(metrics->imageOffsetTop, style, LISTWIDGETITEM_IMAGE_OFFSET_TOP_DLU, 2);
+ WIDGETSTYLE_DLU_TO_HORZ_PX_MIN(metrics->imageOffsetRight, style, LISTWIDGETITEM_IMAGE_OFFSET_RIGHT_DLU, 2);
+ WIDGETSTYLE_DLU_TO_VERT_PX_MIN(metrics->imageOffsetBottom, style, LISTWIDGETITEM_IMAGE_OFFSET_BOTTOM_DLU, 2);
+
+ WIDGETSTYLE_DLU_TO_VERT_PX_MIN(metrics->spacebarOffsetTop, style, LISTWIDGETITEM_SPACEBAR_OFFSET_DLU, 1);
+ WIDGETSTYLE_DLU_TO_VERT_PX_MIN(metrics->spacebarHeight, style, LISTWIDGETITEM_SPACEBAR_HEIGHT_DLU, 2);
+ metrics->spacebarHeight = 14;
+
+ WIDGETSTYLE_DLU_TO_VERT_PX_MIN(metrics->titleOffsetTop, style, LISTWIDGETITEM_TITLE_OFFSET_DLU, 1);
+ WIDGETSTYLE_DLU_TO_HORZ_PX_MIN(metrics->titleMinWidth, style, LISTWIDGETITEM_TITLE_MIN_WIDTH_DLU, 32);
+
+ return TRUE;
+}
+
+static HBITMAP
+ListWidget_GetDeviceBitmap(ifc_device *device, int width, int height,
+ DeviceImageFlags flags, DeviceImage **imageOut)
+{
+ HBITMAP bitmap;
+ wchar_t path[MAX_PATH*2] = {0};
+ const wchar_t *defaultImage;
+
+ DeviceImage *image;
+ DeviceImageCache *imageCache;
+ ifc_devicetype *type;
+
+ if (NULL == device)
+ return NULL;
+
+ imageCache = Plugin_GetImageCache();
+ if (NULL == imageCache)
+ return NULL;
+
+ if (SUCCEEDED(device->GetIcon(path, ARRAYSIZE(path), width, height)))
+ {
+ image = DeviceImageCache_GetImage(imageCache, path, width, height, NULL, NULL);
+ if (NULL != image)
+ {
+ bitmap = DeviceImage_GetBitmap(image, flags);
+ if (NULL != bitmap)
+ {
+ if (NULL != imageOut)
+ *imageOut = image;
+ else
+ DeviceImage_Release(image);
+
+ return bitmap;
+ }
+ }
+ }
+
+ if (NULL != WASABI_API_DEVICES &&
+ S_OK == WASABI_API_DEVICES->TypeFind(device->GetType(), &type))
+ {
+ if (SUCCEEDED(type->GetIcon(path, ARRAYSIZE(path), width, height)))
+ {
+ image = DeviceImageCache_GetImage(imageCache, path, width, height, NULL, NULL);
+ if (NULL != image)
+ {
+ bitmap = DeviceImage_GetBitmap(image, flags);
+ if (NULL != bitmap)
+ {
+ if (NULL != imageOut)
+ *imageOut = image;
+ else
+ DeviceImage_Release(image);
+
+ type->Release();
+ return bitmap;
+ }
+ }
+ }
+ type->Release();
+ }
+
+ defaultImage = Plugin_GetDefaultDeviceImage(width, height);
+ if (NULL != defaultImage)
+ {
+ image = DeviceImageCache_GetImage(imageCache, defaultImage, width, height, NULL, NULL);
+ if (NULL != image)
+ {
+ bitmap = DeviceImage_GetBitmap(image, flags);
+ if (NULL != bitmap)
+ {
+ if (NULL != imageOut)
+ *imageOut = image;
+ else
+ DeviceImage_Release(image);
+ return bitmap;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+HBITMAP
+ListWidget_GetItemImage(ListWidget *self, WidgetStyle *style, ListWidgetItem *item)
+{
+ HBITMAP bitmap;
+
+ if (NULL == item)
+ return NULL;
+
+ if (NULL != item->image)
+ return DeviceImage_GetBitmap(item->image, DeviceImage_Normal);
+
+ if (NULL == self || NULL == style)
+ return NULL;
+
+ ifc_device *device;
+ if (NULL == WASABI_API_DEVICES ||
+ S_OK != WASABI_API_DEVICES->DeviceFind(item->name, &device))
+ {
+ return NULL;
+ }
+
+
+ bitmap = ListWidget_GetDeviceBitmap(device, self->imageSize.cx, self->imageSize.cy,
+ DeviceImage_Normal, &item->image);
+
+ device->Release();
+
+ return bitmap;
+
+}
+
+BOOL
+ListWidget_CalculateItemBaseSize(ListWidget *self, WidgetStyle *style, SIZE *baseSize, long *itemTextWidth)
+{
+ ListWidgetItemMetric metrics;
+
+ if (NULL == baseSize)
+ return FALSE;
+
+ if (FALSE == ListWidget_GetItemMetrics(style, &metrics))
+ ZeroMemory(&metrics, sizeof(metrics));
+
+ baseSize->cx = self->imageSize.cx;
+ baseSize->cy = self->imageSize.cy;
+
+ baseSize->cx += metrics.imageOffsetLeft + metrics.imageOffsetRight;
+ if (baseSize->cx < metrics.titleMinWidth)
+ baseSize->cx = metrics.titleMinWidth;
+
+ if (FALSE != itemTextWidth)
+ *itemTextWidth = baseSize->cx;
+
+ baseSize->cx += metrics.offsetLeft + metrics.offsetRight;
+
+ baseSize->cy += metrics.offsetTop + metrics.offsetBottom +
+ metrics.imageOffsetTop + metrics.imageOffsetBottom +
+ metrics.spacebarHeight + metrics.spacebarOffsetTop +
+ metrics.titleOffsetTop;
+
+ if (FALSE != itemTextWidth)
+ *itemTextWidth = baseSize->cx - (metrics.offsetLeft + metrics.offsetRight);
+
+ return TRUE;
+}
+
+
+
+ListWidgetItem *
+ListWidget_GetItemFromPointEx(ListWidget *self, POINT point,
+ ListWidgetCategory **categoryOut, ListWidgetGroup **groupOut)
+{
+ size_t iCategory, iGroup, iItem;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+
+ if (NULL == self)
+ return NULL;
+
+ for (iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ category = self->categories[iCategory];
+
+ if (FALSE == category->collapsed)
+ {
+ for(iGroup = 0; iGroup < category->groups.size(); iGroup++)
+ {
+ group = category->groups[iGroup];
+ for(iItem = 0; iItem < group->items.size(); iItem++)
+ {
+ item = group->items[iItem];
+
+ if (FALSE != PtInRect(&item->rect, point))
+ {
+ if (NULL != categoryOut)
+ *categoryOut = category;
+
+ if (NULL != groupOut)
+ *groupOut = group;
+
+ return item;
+ }
+ }
+ }
+ }
+ }
+
+ if (NULL != categoryOut)
+ *categoryOut = NULL;
+
+ if (NULL != groupOut)
+ *groupOut = NULL;
+
+ return NULL;
+}
+
+ListWidgetItem *
+ListWidget_GetItemFromPoint(ListWidget *self, POINT point)
+{
+ return ListWidget_GetItemFromPointEx(self, point, NULL, NULL);
+}
+
+ListWidgetItem *
+ListWidget_GetFirstItem(ListWidget *self)
+{
+ size_t iCategory, iGroup;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+
+ if (NULL == self)
+ return NULL;
+
+ for (iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ category = self->categories[iCategory];
+
+ if (FALSE == category->collapsed)
+ {
+ for(iGroup = 0; iGroup < category->groups.size(); iGroup++)
+ {
+ group = category->groups[iGroup];
+ if (group->items.size() > 0)
+ return group->items[0];
+ }
+ }
+ }
+ return NULL;
+}
+
+ListWidgetItem *
+ListWidget_GetLastItem(ListWidget *self)
+{
+ size_t iCategory, iGroup, iItem;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+
+ if (NULL == self)
+ return NULL;
+
+ iCategory = self->categories.size();
+ while(iCategory--)
+ {
+ category = self->categories[iCategory];
+ if (FALSE == category->collapsed)
+ {
+ iGroup = category->groups.size();
+ while(iGroup--)
+ {
+ group = category->groups[iGroup];
+ iItem = group->items.size();
+ if (iItem > 0)
+ return group->items[iItem - 1];
+ }
+ }
+ }
+ return NULL;
+}
+
+ListWidgetItem *
+ListWidget_GetNextItem(ListWidget *self, ListWidgetItem *baseItem)
+{
+ size_t iCategory, iGroup, iItem;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+ BOOL returnNext;
+
+ if (NULL == self || NULL == baseItem)
+ return NULL;
+
+ returnNext = FALSE;
+ for (iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ category = self->categories[iCategory];
+
+ if (FALSE == category->collapsed)
+ {
+ for(iGroup = 0; iGroup < category->groups.size(); iGroup++)
+ {
+ group = category->groups[iGroup];
+ for(iItem = 0; iItem < group->items.size(); iItem++)
+ {
+ item = group->items[iItem];
+ if (item == baseItem)
+ returnNext = TRUE;
+ else if (FALSE != returnNext)
+ return item;
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+ListWidgetItem *
+ListWidget_GetPreviousItem(ListWidget *self, ListWidgetItem *baseItem)
+{
+ size_t iCategory, iGroup, iItem;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+ BOOL returnPrevious;
+
+ if (NULL == self || NULL == baseItem)
+ return NULL;
+
+ returnPrevious = FALSE;
+ iCategory = self->categories.size();
+ while(iCategory--)
+ {
+ category = self->categories[iCategory];
+ if (FALSE == category->collapsed)
+ {
+ iGroup = category->groups.size();
+ while(iGroup--)
+ {
+ group = category->groups[iGroup];
+ iItem = group->items.size();
+ while(iItem--)
+ {
+ item = group->items[iItem];
+ if (item == baseItem)
+ returnPrevious = TRUE;
+ else if (FALSE != returnPrevious)
+ return item;
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+ListWidgetItem *
+ListWidget_GetNextCategoryItem(ListWidget *self, ListWidgetCategory *category, ListWidgetItem *baseItem)
+{
+ size_t iGroup, iItem;
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+ BOOL returnNext;
+
+ if (NULL == self || NULL == baseItem ||
+ NULL == category || FALSE != category->collapsed)
+ {
+ return NULL;
+ }
+
+ returnNext = FALSE;
+
+ for(iGroup = 0; iGroup < category->groups.size(); iGroup++)
+ {
+ group = category->groups[iGroup];
+ for(iItem = 0; iItem < group->items.size(); iItem++)
+ {
+ item = group->items[iItem];
+ if (item == baseItem)
+ returnNext = TRUE;
+ else if (FALSE != returnNext)
+ return item;
+ }
+ }
+
+ return NULL;
+}
+
+ListWidgetItem *
+ListWidget_GetPreviousCategoryItem(ListWidget *self, ListWidgetCategory *category, ListWidgetItem *baseItem)
+{
+ if (NULL == self || NULL == baseItem ||
+ NULL == category || FALSE != category->collapsed)
+ {
+ return NULL;
+ }
+
+ BOOL returnPrevious = FALSE;
+ size_t iGroup = category->groups.size();
+ while(iGroup--)
+ {
+ ListWidgetGroup *group = category->groups[iGroup];
+ size_t iItem = group->items.size();
+ while(iItem--)
+ {
+ ListWidgetItem *item = group->items[iItem];
+ if (item == baseItem)
+ returnPrevious = TRUE;
+ else if (FALSE != returnPrevious)
+ return item;
+ }
+ }
+
+ return NULL;
+}
+
+ListWidgetItem *
+ListWidget_GetNextGroupItem(ListWidget *self, ListWidgetGroup *group, ListWidgetItem *baseItem)
+{
+ if (NULL == self || NULL == baseItem || NULL == group)
+ return NULL;
+
+ BOOL returnNext = FALSE;
+ for(size_t iItem = 0; iItem < group->items.size(); iItem++)
+ {
+ ListWidgetItem *item = group->items[iItem];
+ if (item == baseItem)
+ returnNext = TRUE;
+ else if (FALSE != returnNext)
+ return item;
+ }
+
+ return NULL;
+}
+
+ListWidgetItem *
+ListWidget_GetPreviousGroupItem(ListWidget *self, ListWidgetGroup *group, ListWidgetItem *baseItem)
+{
+ if (NULL == self || NULL == baseItem || NULL == group)
+ return NULL;
+
+ BOOL returnPrevious = FALSE;
+ size_t iItem = group->items.size();
+ while(iItem--)
+ {
+ ListWidgetItem *item = group->items[iItem];
+ if (item == baseItem)
+ returnPrevious = TRUE;
+ else if (FALSE != returnPrevious)
+ return item;
+ }
+
+ return NULL;
+}
+
+ListWidgetItem *
+ListWidget_GetNextLineItem(ListWidget *self, ListWidgetItem *baseItem)
+{
+ size_t iCategory, iGroup, iItem;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+ size_t itemLinePos, itemsPerLine, targetLinePos;
+
+ if (NULL == self || NULL == baseItem)
+ return NULL;
+
+ itemsPerLine = MAX(self->itemsPerLine, 1);
+ targetLinePos = -1;
+
+ for (iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ category = self->categories[iCategory];
+
+ if (FALSE == category->collapsed)
+ {
+ for(iGroup = 0; iGroup < category->groups.size(); iGroup++)
+ {
+ group = category->groups[iGroup];
+ if (-1 == targetLinePos)
+ {
+ itemLinePos = 0;
+ for(iItem = 0; iItem < group->items.size(); iItem++, itemLinePos++)
+ {
+ if (itemLinePos == itemsPerLine)
+ itemLinePos = 0;
+
+ item = group->items[iItem];
+ if (item == baseItem)
+ {
+ size_t test;
+ targetLinePos = itemLinePos;
+ test = iItem + (itemsPerLine - itemLinePos);
+ if (test < group->items.size())
+ {
+ test += targetLinePos;
+ if (test >= group->items.size())
+ test = group->items.size() - 1;
+ return group->items[test];
+ }
+ break;
+ }
+ }
+ }
+ else if (group->items.size() > 0)
+ {
+ size_t test;
+
+ if (targetLinePos < group->items.size())
+ test = targetLinePos;
+ else
+ test = group->items.size() - 1;
+ return group->items[test];
+ }
+
+ }
+ }
+ }
+
+ return NULL;
+}
+
+ListWidgetItem *
+ListWidget_GetPreviousLineItem(ListWidget *self, ListWidgetItem *baseItem)
+{
+ size_t iCategory, iGroup, iItem;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+ size_t itemLinePos, itemsPerLine, targetLinePos;
+
+ if (NULL == self || NULL == baseItem)
+ return NULL;
+
+ itemsPerLine = MAX(self->itemsPerLine, 1);
+ targetLinePos = -1;
+
+ iCategory = self->categories.size();
+ while(iCategory--)
+ {
+ category = self->categories[iCategory];
+
+ if (FALSE == category->collapsed)
+ {
+ iGroup = category->groups.size();
+ while(iGroup--)
+ {
+ group = category->groups[iGroup];
+ if (-1 == targetLinePos)
+ {
+ itemLinePos = 0;
+ for(iItem = 0; iItem < group->items.size(); iItem++, itemLinePos++)
+ {
+ if (itemLinePos == itemsPerLine)
+ itemLinePos = 0;
+
+ item = group->items[iItem];
+ if (item == baseItem)
+ {
+ targetLinePos = itemLinePos;
+ if (iItem >= (itemLinePos + 1))
+ {
+ size_t test = iItem - (itemLinePos + 1);
+ if (test >= (itemsPerLine - (itemLinePos + 1)))
+ test -= itemsPerLine - (itemLinePos + 1);
+ return group->items[test];
+ }
+ break;
+ }
+ }
+ }
+ else if (group->items.size() > 0)
+ {
+ size_t test = group->items.size();
+ test = test/itemsPerLine + ((0 != test%itemsPerLine) ? 0 : - 1);
+ test = test * itemsPerLine;
+ test += targetLinePos;
+ if (test >= group->items.size())
+ test = group->items.size() - 1;
+ return group->items[test];
+ }
+
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static ListWidgetItem *
+ListWidget_FindLastVisibleLine(ListWidget *self, long viewBottom)
+{
+ size_t iCategory, iGroup, iItem;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+ ListWidgetItem *lineItem;
+
+ lineItem = NULL;
+
+ for (iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ category = self->categories[iCategory];
+
+ if (FALSE == category->collapsed)
+ {
+ for(iGroup = 0; iGroup < category->groups.size(); iGroup++)
+ {
+ group = category->groups[iGroup];
+ for(iItem = 0; iItem < group->items.size(); iItem++)
+ {
+ item = group->items[iItem];
+ if (item->rect.top < viewBottom)
+ {
+ if (NULL == lineItem ||
+ item->rect.top != lineItem->rect.top)
+ {
+ if (item->rect.bottom <= viewBottom)
+ lineItem = item;
+ else
+ return (NULL != lineItem) ? lineItem : item;
+ }
+ }
+ else
+ return lineItem;
+ }
+ }
+ }
+ }
+
+ return lineItem;
+}
+
+static ListWidgetItem *
+ListWidget_FindFirstVisibleLine(ListWidget *self, long viewTop)
+{
+ size_t iCategory, iGroup, iItem;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+ ListWidgetItem *lineItem;
+
+ lineItem = NULL;
+
+ for (iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ category = self->categories[iCategory];
+
+ if (FALSE == category->collapsed)
+ {
+ for(iGroup = 0; iGroup < category->groups.size(); iGroup++)
+ {
+ group = category->groups[iGroup];
+ for(iItem = 0; iItem < group->items.size(); iItem++)
+ {
+ item = group->items[iItem];
+ if (NULL == lineItem ||
+ item->rect.top != lineItem->rect.top)
+ {
+ lineItem = item;
+ if (item->rect.top >= viewTop)
+ return lineItem;
+ }
+ }
+ }
+ }
+ }
+
+ return lineItem;
+}
+
+static ListWidgetItem *
+ListWidget_FindNextLine(ListWidget *self, ListWidgetItem *baseItem)
+{
+ size_t iCategory, iGroup, iItem;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+ BOOL foundItem;
+
+ foundItem = FALSE;
+
+ for (iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ category = self->categories[iCategory];
+
+ if (FALSE == category->collapsed)
+ {
+ for(iGroup = 0; iGroup < category->groups.size(); iGroup++)
+ {
+ group = category->groups[iGroup];
+ for(iItem = 0; iItem < group->items.size(); iItem++)
+ {
+ item = group->items[iItem];
+ if (item == baseItem)
+ foundItem = TRUE;
+ if (FALSE != foundItem &&
+ item->rect.top != baseItem->rect.top)
+ {
+ return item;
+ }
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+static ListWidgetItem *
+ListWidget_FindPreviousLine(ListWidget *self, ListWidgetItem *baseItem)
+{
+ size_t iCategory, iGroup, iItem;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+ BOOL foundItem;
+
+ foundItem = FALSE;
+
+ iCategory = self->categories.size();
+ while(iCategory--)
+ {
+ category = self->categories[iCategory];
+
+ if (FALSE == category->collapsed)
+ {
+ iGroup = category->groups.size();
+ while(iGroup--)
+ {
+ group = category->groups[iGroup];
+ iItem = group->items.size();
+ while(iItem--)
+ {
+ item = group->items[iItem];
+ if (item == baseItem)
+ foundItem = TRUE;
+ if (FALSE != foundItem &&
+ item->rect.top != baseItem->rect.top)
+ {
+ return item;
+ }
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static ListWidgetItem *
+ListWidget_FindLineItemAtPos(ListWidget *self, ListWidgetItem *beginLine, ListWidgetItem *linePosition)
+{
+ size_t iCategory, iGroup, iItem;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+ BOOL foundLine;
+
+ foundLine = FALSE;
+
+ for (iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ category = self->categories[iCategory];
+
+ if (FALSE == category->collapsed)
+ {
+ for(iGroup = 0; iGroup < category->groups.size(); iGroup++)
+ {
+ group = category->groups[iGroup];
+ for(iItem = 0; iItem < group->items.size(); iItem++)
+ {
+ item = group->items[iItem];
+ if (item == beginLine)
+ foundLine = TRUE;
+
+ if (FALSE != foundLine)
+ {
+ if (beginLine->rect.top == linePosition->rect.top)
+ {
+ if (item->rect.top != beginLine->rect.top)
+ {
+ return (iItem > 0) ? group->items[iItem - 1] : beginLine;
+ }
+ }
+ else
+ {
+ if (item->rect.left == linePosition->rect.left)
+ return item;
+ }
+ }
+ }
+ if (FALSE != foundLine)
+ {
+ if (group->items.size() > 0)
+ return group->items[group->items.size() - 1];
+ return NULL;
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+ListWidgetItem *
+ListWidget_GetNextPageItem(ListWidget *self, HWND hwnd, ListWidgetItem *baseItem)
+{
+ ListWidgetItem *lineItem;
+ RECT rect;
+ POINT origin;
+ long viewBottom;
+
+ if (NULL == self || NULL == baseItem)
+ return NULL;
+
+ if (FALSE == GetClientRect(hwnd, &rect))
+ return NULL;
+
+ if (FALSE != ListWidget_GetViewOrigin(hwnd, &origin))
+ OffsetRect(&rect, -origin.x, -origin.y);
+
+ if (baseItem->rect.bottom < rect.top)
+ viewBottom = baseItem->rect.top + RECTHEIGHT(rect);
+ else
+ viewBottom = rect.bottom;
+
+ lineItem = ListWidget_FindLastVisibleLine(self, viewBottom);
+ if (NULL == lineItem)
+ return NULL;
+
+ if (lineItem->rect.top <= baseItem->rect.top)
+ {
+ viewBottom = baseItem->rect.top + RECTHEIGHT(rect);
+ lineItem = ListWidget_FindLastVisibleLine(self, viewBottom);
+ if (NULL == lineItem)
+ return NULL;
+ if (lineItem->rect.top <= baseItem->rect.top)
+ {
+ lineItem = ListWidget_FindNextLine(self, baseItem);
+ if (NULL == lineItem)
+ return NULL;
+ }
+ }
+
+ return ListWidget_FindLineItemAtPos(self, lineItem, baseItem);
+}
+
+ListWidgetItem *
+ListWidget_GetPreviousPageItem(ListWidget *self, HWND hwnd, ListWidgetItem *baseItem)
+{
+ ListWidgetItem *lineItem;
+ RECT rect;
+ POINT origin;
+ long viewTop;
+
+ if (NULL == self || NULL == baseItem)
+ return NULL;
+
+ if (FALSE == GetClientRect(hwnd, &rect))
+ return NULL;
+
+ if (FALSE != ListWidget_GetViewOrigin(hwnd, &origin))
+ OffsetRect(&rect, -origin.x, -origin.y);
+
+ if (baseItem->rect.top > rect.bottom)
+ viewTop = baseItem->rect.bottom - RECTHEIGHT(rect);
+ else
+ viewTop = rect.top;
+
+ lineItem = ListWidget_FindFirstVisibleLine(self, viewTop);
+ if (NULL == lineItem)
+ return NULL;
+
+ if (lineItem->rect.top >= baseItem->rect.top)
+ {
+ viewTop = baseItem->rect.bottom - RECTHEIGHT(rect);
+ lineItem = ListWidget_FindFirstVisibleLine(self, viewTop);
+ if (NULL == lineItem)
+ return NULL;
+ if (lineItem->rect.top >= baseItem->rect.top)
+ {
+ lineItem = ListWidget_FindPreviousLine(self, baseItem);
+ if (NULL == lineItem)
+ return NULL;
+ }
+ }
+
+ return ListWidget_FindLineItemAtPos(self, lineItem, baseItem);
+}
+
+BOOL
+ListWidget_EnsureItemVisisble(ListWidget *self, HWND hwnd, ListWidgetItem *item, ListWidgetVisibleFlags flags)
+{
+ RECT rect;
+ POINT pt;
+ int dx, dy;
+
+ if (NULL == self || NULL == item || NULL == hwnd)
+ return FALSE;
+
+ if (FALSE == GetClientRect(hwnd, &rect))
+ return FALSE;
+
+ if (FALSE != ListWidget_GetViewOrigin(hwnd, &pt))
+ OffsetRect(&rect, -pt.x, -pt.y);
+
+ if (0 == (VISIBLE_ALIGN_ALWAYS & flags))
+ {
+ if (item->rect.left >= rect.left &&
+ item->rect.right <= rect.right &&
+ item->rect.top >= rect.top &&
+ item->rect.bottom <= rect.bottom)
+ {
+ return FALSE;
+ }
+ }
+
+ if (0 != (VISIBLE_PARTIAL_OK & flags))
+ {
+ if (item->rect.left < rect.right &&
+ item->rect.right > rect.left &&
+ item->rect.top < rect.bottom &&
+ item->rect.bottom > rect.top)
+ {
+ return FALSE;
+ }
+ }
+
+ if (item->rect.right > rect.right)
+ dx = item->rect.right - rect.right;
+ else
+ dx = 0;
+
+ if ((item->rect.left - dx) < rect.left)
+ dx = item->rect.left - rect.left;
+
+ dy = 0;
+ if (0 != (VISIBLE_ALIGN_TOP & flags))
+ {
+ dy = item->rect.bottom - rect.bottom;
+ }
+ else if (0 != (VISIBLE_ALIGN_BOTTOM & flags))
+ {
+ SCROLLINFO scrollInfo;
+ scrollInfo.cbSize = sizeof(scrollInfo);
+ scrollInfo.fMask = SIF_RANGE | SIF_PAGE;
+ if (FALSE != GetScrollInfo(hwnd, SB_VERT, &scrollInfo))
+ {
+ dy = scrollInfo.nMax - rect.bottom;
+ }
+ }
+
+ if ((item->rect.bottom - dy) > rect.bottom)
+ dy = item->rect.bottom - rect.bottom;
+
+ if ((item->rect.top - dy) < rect.top)
+ dy = item->rect.top - rect.top;
+
+ if (0 == dx && 0 == dy)
+ return FALSE;
+
+ if (FALSE == WIDGET_SCROLL(hwnd, dx, dy, TRUE))
+ return FALSE;
+
+ ListWidget_UpdateHover(self, hwnd);
+
+ return TRUE;
+}
+
+BOOL
+ListWidget_AddItem(ListWidgetGroup *group, ListWidgetItem *item)
+{
+ if (NULL == group || NULL == item)
+ return FALSE;
+
+ group->items.push_back(item);
+ return TRUE;
+
+}
+
+
+ListWidgetItem *
+ListWidget_FindGroupItemEx(ListWidgetGroup *group, const char *name, size_t max)
+{
+ size_t index, count;
+
+ if (NULL == group || NULL == name)
+ return NULL;
+
+ count = group->items.size();
+ if (max < count)
+ count = max;
+
+ for(index = 0; index < count; index++)
+ {
+ ListWidgetItem *item = group->items[index];
+ if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, 0, name, -1, item->name, -1))
+ return item;
+ }
+
+ return NULL;
+}
+
+ListWidgetItem *
+ListWidget_FindGroupItem(ListWidgetGroup *group, const char *name)
+{
+ return ListWidget_FindGroupItemEx(group, name, -1);
+}
+
+
+ListWidgetGroup *
+ListWidget_GetItemOwner(ListWidget *self, ListWidgetItem *baseItem, ListWidgetCategory **categoryOut)
+{
+ size_t iCategory, iGroup, iItem;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+
+ if (NULL == self || NULL == baseItem)
+ return NULL;
+
+ for (iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ category = self->categories[iCategory];
+
+ for(iGroup = 0; iGroup < category->groups.size(); iGroup++)
+ {
+ group = category->groups[iGroup];
+ for(iItem = 0; iItem < group->items.size(); iItem++)
+ {
+ item = group->items[iItem];
+
+ if (item == baseItem)
+ {
+ if (NULL != categoryOut)
+ *categoryOut = category;
+
+ return group;
+ }
+ }
+ }
+ }
+
+ if (NULL != categoryOut)
+ *categoryOut = NULL;
+
+ return NULL;
+}
+
+ListWidgetItem *
+ListWidget_FindItem(ListWidget *self, const char *name,
+ ListWidgetCategory **categoryOut,
+ ListWidgetGroup **groupOut)
+{
+ size_t iCategory, iGroup, iItem;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+ ListWidgetItem *item;
+
+ if (NULL == self || NULL == name)
+ return NULL;
+
+ for (iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ category = self->categories[iCategory];
+
+ for(iGroup = 0; iGroup < category->groups.size(); iGroup++)
+ {
+ group = category->groups[iGroup];
+ for(iItem = 0; iItem < group->items.size(); iItem++)
+ {
+ item = group->items[iItem];
+
+ if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, 0, name, -1, item->name, -1))
+ {
+ if (NULL != categoryOut)
+ *categoryOut = category;
+
+ if (NULL != groupOut)
+ *groupOut = group;
+
+ return item;
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+BOOL
+ListWidget_FindItemPos(ListWidget *self, ListWidgetItem *item,
+ size_t *categoryOut, size_t *groupOut, size_t *itemOut)
+{
+ size_t iCategory, iGroup, iItem;
+ ListWidgetCategory *category;
+ ListWidgetGroup *group;
+
+ if (NULL == self || NULL == item)
+ return FALSE;
+
+ for (iCategory = 0; iCategory < self->categories.size(); iCategory++)
+ {
+ category = self->categories[iCategory];
+
+ for(iGroup = 0; iGroup < category->groups.size(); iGroup++)
+ {
+ group = category->groups[iGroup];
+ for(iItem = 0; iItem < group->items.size(); iItem++)
+ {
+ if (item == group->items[iItem])
+ {
+ if (NULL != categoryOut)
+ *categoryOut = iCategory;
+
+ if (NULL != groupOut)
+ *groupOut = iGroup;
+
+ if (NULL != itemOut)
+ *itemOut = iItem;
+
+ return TRUE;
+ }
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+BOOL
+ListWidget_DisplayItemContextMenu(ListWidget *self, HWND hwnd, ListWidgetItem *item, POINT pt)
+{
+ HMENU menu;
+ ifc_device *device;
+ unsigned int commandId;
+ BOOL succeeded;
+ char *itemName;
+
+
+ if (NULL == self || NULL == item)
+ return FALSE;
+
+ if (NULL != self->activeMenu)
+ return FALSE;
+
+ if (NULL == WASABI_API_DEVICES ||
+ S_OK != WASABI_API_DEVICES->DeviceFind(item->name, &device))
+ {
+ return FALSE;
+ }
+
+ menu = CreatePopupMenu();
+ if (NULL != menu)
+ {
+ if (0 == Menu_InsertDeviceItems(menu, 0, 100, device, DeviceCommandContext_ViewMenu))
+ {
+ DestroyMenu(menu);
+ menu = NULL;
+ }
+ }
+
+ device->Release();
+
+ if (NULL == menu)
+ return FALSE;
+
+ succeeded = FALSE;
+
+ self->activeMenu = menu;
+ itemName = AnsiString_Duplicate(item->name);
+
+ if (FALSE != ListWidget_RemoveHover(self, hwnd, TRUE))
+ UpdateWindow(hwnd);
+
+ commandId = Menu_TrackPopup(Plugin_GetLibraryWindow(), menu,
+ TPM_LEFTALIGN | TPM_TOPALIGN | TPM_VERPOSANIMATION | TPM_VERTICAL | TPM_RETURNCMD,
+ pt.x, pt.y, hwnd, NULL);
+
+
+ self->activeMenu = NULL;
+
+ if (0 != commandId)
+ {
+ const char *command;
+
+ command = (const char*)Menu_GetItemData(menu, commandId, FALSE);
+ succeeded = ListWidget_SendItemCommand(itemName, command, hwnd, 0, TRUE);
+ }
+ else
+ {
+ if (ERROR_SUCCESS == GetLastError())
+ succeeded = TRUE;
+ }
+
+ Menu_FreeItemData(menu, 0, -1);
+
+ AnsiString_Free(itemName);
+
+ if (FALSE != ListWidget_UpdateHover(self, hwnd))
+ UpdateWindow(hwnd);
+
+ return succeeded;
+}
+
+size_t
+ListWidget_GetItemCommands(ListWidgetItem *item, ListWidgetCommand **buffer, size_t bufferMax)
+{
+ size_t count;
+ ifc_device *device;
+
+ if (NULL == item)
+ return 0;
+
+ count = 0;
+
+ if (NULL != WASABI_API_DEVICES &&
+ S_OK == WASABI_API_DEVICES->DeviceFind(item->name, &device))
+ {
+ count = ListWigdet_GetDeviceCommands(buffer, bufferMax, device);
+ device->Release();
+ }
+
+ return count;
+}
+
+BOOL
+ListWidget_SendItemCommand(const char *name, const char *command, HWND hostWindow, ULONG_PTR param, BOOL enableIntercept)
+{
+ BOOL succeeded;
+ ifc_device *device;
+ BOOL commandProcessed;
+
+ if (NULL == name ||
+ NULL == command ||
+ NULL == WASABI_API_DEVICES ||
+ S_OK != WASABI_API_DEVICES->DeviceFind(name, &device))
+ {
+ return FALSE;
+ }
+
+ commandProcessed = FALSE;
+ succeeded = FALSE;
+
+ if (FALSE != enableIntercept)
+ {
+ if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, 0, command, -1, "view_open", -1))
+ {
+ succeeded = Navigation_SelectDevice(device->GetName());
+ commandProcessed = succeeded;
+ }
+ else if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, 0, command, -1, "rename", -1))
+ {
+ succeeded = Navigation_EditDeviceTitle(device->GetName());
+ commandProcessed = succeeded;
+ }
+ }
+
+ if (FALSE == commandProcessed &&
+ SUCCEEDED(device->SendCommand(command, hostWindow, param)))
+ {
+ succeeded = TRUE;
+ }
+
+ device->Release();
+
+ return succeeded;
+}
+
+BOOL
+ListWidget_CreateItemActivity(ListWidgetItem *item)
+{
+ if (NULL == item)
+ return FALSE;
+
+ if (NULL == item->activity)
+ {
+ item->activity = (ListWidgetActivity*)malloc(sizeof(ListWidgetActivity));
+ if (NULL == item->activity)
+ return FALSE;
+ }
+
+ item->activity->step = 0;
+ item->activity->cancelable = FALSE;
+ item->activity->percent = (unsigned int)-1;
+ item->activity->title = NULL;
+ SetSizeEmpty(&item->activity->titleSize);
+
+ return TRUE;
+}
+
+BOOL
+ListWidget_DeleteItemActivity(ListWidgetItem *item)
+{
+ if (NULL == item ||
+ NULL == item->activity)
+ {
+ return FALSE;
+ }
+
+ String_Free(item->activity->title);
+
+ free(item->activity);
+ item->activity = NULL;
+
+ return TRUE;
+}
+
+
+ListWidtetActivityChange
+ListWidget_UpdateItemActivity(ListWidgetItem *item, ifc_deviceactivity *activity)
+{
+ ListWidgetActivityChange changed;
+ BOOL cancelable;
+ unsigned int percent;
+ wchar_t buffer[512] = {0};
+
+ if (NULL == item || NULL == item->activity || NULL == activity)
+ return ListWidgetActivityChanged_Nothing;
+
+ changed = ListWidgetActivityChanged_Nothing;
+
+ cancelable = activity->GetCancelable();
+ if (item->activity->cancelable != cancelable)
+ {
+ changed |= ListWidgetActivityChanged_Cancelable;
+ item->activity->cancelable = cancelable;
+ }
+
+ if(FAILED(activity->GetProgress(&percent)))
+ percent = (unsigned int)-1;
+
+ if (item->activity->percent != percent)
+ {
+ changed |= ListWidgetActivityChanged_Percent;
+ item->activity->percent = percent;
+
+ }
+
+ if (FAILED(activity->GetDisplayName(buffer, ARRAYSIZE(buffer))))
+ buffer[0] = L'\0';
+
+ if (NULL == item->activity->title ||
+ CSTR_EQUAL != CompareString(LOCALE_SYSTEM_DEFAULT, 0, item->activity->title, -1, buffer, -1))
+ {
+ changed |= ListWidgetActivityChanged_Title;
+
+ String_Free(item->activity->title);
+ item->activity->title = String_Duplicate(buffer);
+ SetSizeEmpty(&item->activity->titleSize);
+ }
+
+ return changed;
+}
+
+BOOL
+ListWidget_GetItemImageRect(ListWidget *self, ListWidgetItem *item, ListWidgetItemMetric *metrics, RECT *rect)
+{
+ if (NULL == item || NULL == rect)
+ return FALSE;
+
+ if (FALSE == CopyRect(rect, &item->rect))
+ return FALSE;
+
+ if (NULL != metrics)
+ {
+ rect->left += metrics->offsetLeft + metrics->imageOffsetLeft;
+ rect->top += metrics->offsetTop + metrics->imageOffsetTop;
+ rect->right -= metrics->offsetRight - metrics->imageOffsetRight;
+ rect->bottom = rect->top + self->imageSize.cy;
+ }
+
+ return TRUE;
+}
+
+BOOL
+ListWidget_GetItemFrameRect(ListWidget *self, ListWidgetItem *item, ListWidgetItemMetric *metrics, RECT *rect)
+{
+ if (NULL == item || NULL == rect)
+ return FALSE;
+
+ if (FALSE == CopyRect(rect, &item->rect))
+ return FALSE;
+
+ if (NULL != metrics)
+ {
+ rect->bottom = rect->top + metrics->offsetTop +
+ metrics->imageOffsetTop + metrics->imageOffsetBottom +
+ self->imageSize.cy;
+ }
+
+ return TRUE;
+}
+
+static BOOL
+ListWidget_CalcItemActivityTitleSize(ListWidget *self, HDC hdc, ListWidgetActivity *activity)
+{
+ BOOL result;
+ HDC windowDC;
+ HFONT prevFont;
+ RECT rect;
+
+
+ if (NULL == hdc)
+ {
+ windowDC = GetDCEx(NULL, NULL, DCX_WINDOW | DCX_CACHE | DCX_NORESETATTRS);
+ if (NULL == windowDC)
+ {
+ SetSizeEmpty(&activity->titleSize);
+ return FALSE;
+ }
+ hdc = windowDC;
+ }
+ else
+ windowDC = NULL;
+
+ prevFont = SelectFont(hdc, self->activityFont);
+
+ SetRect(&rect, 0, 0, self->activityMetrics.titleWidth, self->activityMetrics.titleHeight);
+ result = DrawText(hdc, activity->title, -1, &rect,
+ DT_CALCRECT | DT_NOPREFIX | DT_WORDBREAK | DT_EDITCONTROL | DT_WORD_ELLIPSIS);
+
+ if (FALSE == result)
+ SetSizeEmpty(&activity->titleSize);
+ else
+ {
+ TEXTMETRIC textMetrics;
+ if (FALSE == GetTextMetrics(hdc, &textMetrics))
+ ZeroMemory(&textMetrics, sizeof(textMetrics));
+
+ if (rect.right > self->activityMetrics.titleWidth)
+ rect.right = self->activityMetrics.titleWidth;
+ if (rect.bottom > self->activityMetrics.titleHeight)
+ {
+ textMetrics.tmHeight = self->activityMetrics.fontHeight;
+ rect.bottom = (self->activityMetrics.titleHeight/textMetrics.tmHeight)*textMetrics.tmHeight;
+ }
+
+ activity->titleSize.cx = rect.right + textMetrics.tmAveCharWidth/2;
+ activity->titleSize.cy = rect.bottom;
+ }
+
+ SelectFont(hdc, prevFont);
+ if (NULL != windowDC)
+ ReleaseDC(NULL, windowDC);
+
+ return result;
+}
+
+static BOOL
+ListWidget_GetItemActivityWorkRect(ListWidget *self, HDC hdc,
+ ListWidgetItem *item, ListWidgetItemMetric *metrics, RECT *rect)
+{
+ ListWidgetActivityMetric *activityMetrics;
+ long length;
+
+ if (FALSE == ListWidget_GetItemActivityRect(self, item, metrics, rect))
+ return FALSE;
+
+ if (0 == item->activity->titleSize.cy &&
+ FALSE == ListWidget_CalcItemActivityTitleSize(self, hdc, item->activity))
+ {
+ return FALSE;
+ }
+
+ activityMetrics = &self->activityMetrics;
+
+ length = 0;
+ if (0 != activityMetrics->progressWidth)
+ {
+ if (0 != length)
+ length += activityMetrics->spacing;
+ length += activityMetrics->progressWidth;
+ }
+
+ if (0 != item->activity->titleSize.cx)
+ {
+ if (0 != length)
+ length += activityMetrics->spacing;
+ length += item->activity->titleSize.cx;
+ }
+
+ if (0 != activityMetrics->percentWidth)
+ {
+ if (0 != length)
+ length += activityMetrics->spacing;
+ length += activityMetrics->percentWidth;
+ }
+
+ rect->top += activityMetrics->offsetTop;
+ rect->bottom -= activityMetrics->offsetBottom;
+
+ rect->left += activityMetrics->offsetLeft;
+ rect->right -= activityMetrics->offsetRight;
+
+ rect->left += ((rect->right - rect->left) - length)/2;
+ rect->right = rect->left + length;
+
+ return TRUE;
+}
+
+
+BOOL
+ListWidget_GetItemActivityRect(ListWidget *self, ListWidgetItem *item, ListWidgetItemMetric *metrics, RECT *rect)
+{
+ if (NULL == self ||
+ NULL == item ||
+ NULL == metrics ||
+ NULL == rect)
+ {
+ return FALSE;
+ }
+
+ rect->bottom = item->rect.top + self->imageSize.cy /*+ metrics.imageOffsetBottom*/;
+ rect->bottom += metrics->offsetTop + metrics->imageOffsetTop;
+ rect->top = rect->bottom - self->activityMetrics.height;
+
+ rect->left = item->rect.left + (self->itemWidth - self->activityMetrics.width)/2;
+ rect->right = rect->left + self->activityMetrics.width;
+
+ return TRUE;
+}
+
+BOOL
+ListWidget_GetItemActivityProgressRect(ListWidget *self, HDC hdc,
+ ListWidgetItem *item, ListWidgetItemMetric *metrics, RECT *rect)
+{
+ if (0 == self->activityMetrics.progressWidth ||
+ FALSE == ListWidget_GetItemActivityWorkRect(self, hdc, item, metrics, rect))
+ {
+ return FALSE;
+ }
+
+ rect->right = rect->left + self->activityMetrics.progressWidth;
+ rect->top +=((rect->bottom - rect->top) - self->activityMetrics.progressHeight)/2;
+ rect->bottom = rect->top + self->activityMetrics.progressHeight;
+ return TRUE;
+}
+
+BOOL
+ListWidget_GetItemActivityPercentRect(ListWidget *self, HDC hdc,
+ ListWidgetItem *item, ListWidgetItemMetric *metrics, RECT *rect)
+{
+ if (0 == self->activityMetrics.percentWidth ||
+ FALSE == ListWidget_GetItemActivityWorkRect(self, hdc, item, metrics, rect))
+ {
+ return FALSE;
+ }
+
+ rect->left = rect->right - self->activityMetrics.percentWidth;
+ rect->top += ((rect->bottom - rect->top) - self->activityMetrics.percentHeight)/2;
+ rect->bottom = rect->top + self->activityMetrics.percentHeight;
+
+ return TRUE;
+}
+
+BOOL
+ListWidget_GetItemActivityTitleRect(ListWidget *self, HDC hdc,
+ ListWidgetItem *item, ListWidgetItemMetric *metrics, RECT *rect)
+{
+ if (0 == self->activityMetrics.titleWidth ||
+ FALSE == ListWidget_GetItemActivityWorkRect(self, hdc, item, metrics, rect))
+ {
+ return FALSE;
+ }
+
+ if (0 != self->activityMetrics.progressWidth)
+ rect->left += self->activityMetrics.progressWidth + self->activityMetrics.spacing;
+
+ rect->right = rect->left + item->activity->titleSize.cx;
+
+ rect->top += ((rect->bottom - rect->top) - item->activity->titleSize.cy)/2;
+ rect->bottom = rect->top + item->activity->titleSize.cy;
+
+ return TRUE;
+}
+
+BOOL
+ListWidget_InvalidateItemActivity(ListWidget *self, HWND hwnd, ListWidgetItem *item, ListWidgetActivityChange changes)
+{
+ ListWidgetItemMetric metrics;
+ WidgetStyle *style;
+ POINT origin;
+ RECT rect;
+ BOOL invalidated;
+
+
+ if (ListWidgetActivityChanged_Nothing == changes)
+ return FALSE;
+
+ style = WIDGET_GET_STYLE(hwnd);
+
+
+ if (NULL == style ||
+ FALSE == ListWidget_GetItemMetrics(style, &metrics))
+ {
+ return FALSE;
+ }
+
+ if (FALSE == ListWidget_GetViewOrigin(hwnd, &origin))
+ {
+ origin.x = 0;
+ origin.y = 0;
+ }
+
+ invalidated = FALSE;
+
+ if (0 != (ListWidgetActivityChanged_Percent & changes))
+ {
+ if (FALSE != ListWidget_GetItemActivityPercentRect(self, NULL, item, &metrics, &rect))
+ {
+ OffsetRect(&rect, origin.x, origin.y);
+ if (FALSE != InvalidateRect(hwnd, &rect, FALSE))
+ invalidated = TRUE;
+ }
+ }
+
+ if (0 != (ListWidgetActivityChanged_Title & changes))
+ {
+ if (FALSE != ListWidget_GetItemActivityTitleRect(self, NULL, item, &metrics, &rect))
+ {
+ OffsetRect(&rect, origin.x, origin.y);
+ if (FALSE != InvalidateRect(hwnd, &rect, FALSE))
+ invalidated = TRUE;
+ }
+ }
+
+ return invalidated;
+}
+
+BOOL
+ListWidget_InvalidateItemImage(ListWidget *self, HWND hwnd, ListWidgetItem *item)
+{
+ ListWidgetItemMetric metrics;
+ WidgetStyle *style;
+ POINT origin;
+ RECT rect;
+
+ style = WIDGET_GET_STYLE(hwnd);
+
+ if (NULL == style ||
+ FALSE == ListWidget_GetItemMetrics(style, &metrics))
+ {
+ return FALSE;
+ }
+
+ if (FALSE == ListWidget_GetItemImageRect(self, item, &metrics, &rect))
+ return FALSE;
+
+ if (FALSE != ListWidget_GetViewOrigin(hwnd, &origin))
+ OffsetRect(&rect, origin.x, origin.y);
+
+ return InvalidateRect(hwnd, &rect, FALSE);
+}
+
+
+BOOL
+ListWidget_GetItemSpacebarRect(ListWidget *self, ListWidgetItem *item, ListWidgetItemMetric *metrics, RECT *rect)
+{
+ if (NULL == item || NULL == rect)
+ return FALSE;
+
+ if (0 == item->spaceTotal ||
+ FALSE == CopyRect(rect, &item->rect))
+ {
+ return FALSE;
+ }
+
+ if (NULL != metrics)
+ {
+ rect->left += metrics->offsetLeft;
+ rect->top += metrics->offsetTop + metrics->imageOffsetTop +
+ self->imageSize.cy + metrics->imageOffsetBottom +
+ metrics->spacebarOffsetTop;
+ rect->right -= metrics->offsetRight;
+ rect->bottom = rect->top + metrics->spacebarHeight;
+ }
+
+ return TRUE;
+}
+
+BOOL
+ListWidget_GetItemTitleRect(ListWidget *self, ListWidgetItem *item, ListWidgetItemMetric *metrics, BOOL exactSize, RECT *rect)
+{
+ if (NULL == item || NULL == rect)
+ return FALSE;
+
+ if (FALSE == CopyRect(rect, &item->rect))
+ {
+ return FALSE;
+ }
+
+ if (NULL != metrics)
+ {
+ rect->left += metrics->offsetLeft;
+ rect->top += metrics->offsetTop + metrics->imageOffsetTop +
+ self->imageSize.cy + metrics->imageOffsetBottom;
+
+ if (0 != item->spaceTotal)
+ rect->top += metrics->spacebarOffsetTop + metrics->spacebarHeight;
+
+ rect->top += metrics->titleOffsetTop;
+ rect->right -= metrics->offsetRight;
+ rect->bottom -= metrics->offsetBottom;
+ }
+
+ if (-1 != item->titleSize.cy)
+ {
+ long max;
+
+ if (FALSE != exactSize)
+ {
+ max = rect->right - rect->left;
+ if (max > item->titleSize.cx)
+ {
+ rect->left += (max - item->titleSize.cx)/2;
+ rect->right = rect->left + item->titleSize.cx;
+ }
+ }
+
+ max = rect->top + item->titleSize.cy;
+ if (rect->bottom > max)
+ rect->bottom = max;
+ }
+
+ return TRUE;
+}
+
+BOOL
+ListWidget_GetItemConnectionRect(ListWidget *self, ListWidgetItem *item, ListWidgetItemMetric *metrics, RECT *rect)
+{
+ if (NULL == item || NULL == rect)
+ return FALSE;
+
+ if (NULL == metrics)
+ return FALSE;
+
+ SetRect(rect, 0, 0, self->connectionSize.cx, self->connectionSize.cy);
+ OffsetRect(rect,
+ item->rect.right - metrics->offsetRight - metrics->imageOffsetRight - rect->right - 2,
+ item->rect.top + metrics->offsetTop + metrics->imageOffsetTop + self->imageSize.cy - rect->bottom - 2);
+
+ if (rect->left < (item->rect.left + metrics->offsetLeft) ||
+ rect->top < (item->rect.top + metrics->offsetTop))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+ListWidgetItemPart
+ListWidget_GetItemPartFromPoint(ListWidget *self, ListWidgetItem *item, ListWidgetItemMetric *metrics,
+ POINT pt, ListWidgetItemPart mask, RECT *partRect)
+{
+ RECT rect;
+
+ if (NULL == self ||
+ NULL == item ||
+ NULL == metrics)
+ {
+ if (NULL != partRect)
+ SetRectEmpty(partRect);
+
+ return ListWidgetItemPart_None;
+ }
+
+
+
+ if (NULL != item->activity &&
+ FALSE != ListWidget_GetItemActivityRect(self, item, metrics, &rect) &&
+ FALSE != PtInRect(&rect, pt))
+ {
+ if (0 != (ListWidgetItemPart_Activity & mask))
+ {
+ if (NULL != partRect)
+ CopyRect(partRect, &rect);
+
+ return ListWidgetItemPart_Activity;
+ }
+
+ mask &= ~(ListWidgetItemPart_Command | ListWidgetItemPart_Connection);
+ }
+
+ if (0 != (ListWidgetItemPart_Command & mask) &&
+ FALSE != ListWidgetItem_IsInteractive(item))
+ {
+
+ size_t index = self->commandsCount;
+ while(index--)
+ {
+ if (FALSE != ListWidget_GetCommandRect(self->commands[index], &rect))
+ {
+ OffsetRect(&rect, item->rect.left, item->rect.top);
+ if (FALSE != PtInRect(&rect, pt))
+ {
+ if (FALSE == ListWidget_GetCommandDisabled(self->commands[index]))
+ {
+ if (NULL != partRect)
+ CopyRect(partRect, &rect);
+
+ return ListWidgetItemPart_Command;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ if (0 != (ListWidgetItemPart_Connection & mask) &&
+ FALSE != ListWidget_GetItemConnectionRect(self, item, metrics, &rect) &&
+ FALSE != PtInRect(&rect, pt))
+ {
+ if (NULL != partRect)
+ CopyRect(partRect, &rect);
+ return ListWidgetItemPart_Connection;
+ }
+
+ if (0 != (ListWidgetItemPart_Image & mask) &&
+ FALSE != ListWidget_GetItemImageRect(self, item, metrics, &rect) &&
+ FALSE != PtInRect(&rect, pt))
+ {
+ if (NULL != partRect)
+ CopyRect(partRect, &rect);
+ return ListWidgetItemPart_Image;
+ }
+
+ if (0 != (ListWidgetItemPart_Frame & mask) &&
+ FALSE != ListWidget_GetItemFrameRect(self, item, metrics, &rect) &&
+ FALSE != PtInRect(&rect, pt))
+ {
+ if (NULL != partRect)
+ CopyRect(partRect, &rect);
+ return ListWidgetItemPart_Frame;
+ }
+
+
+ if (0 != (ListWidgetItemPart_Spacebar & mask) &&
+ FALSE != ListWidget_GetItemSpacebarRect(self, item, metrics, &rect) &&
+ FALSE != PtInRect(&rect, pt))
+ {
+ if (NULL != partRect)
+ CopyRect(partRect, &rect);
+ return ListWidgetItemPart_Spacebar;
+ }
+
+ if (0 != (ListWidgetItemPart_Title & mask) &&
+ FALSE != ListWidget_GetItemTitleRect(self, item, metrics, FALSE, &rect) &&
+ FALSE != PtInRect(&rect, pt))
+ {
+ if (NULL != partRect)
+ CopyRect(partRect, &rect);
+ return ListWidgetItemPart_Title;
+ }
+
+ return ListWidgetItemPart_None;
+}
+
+
+
+static BOOL
+ListWidget_FilterItemTitle(wchar_t *buffer, size_t bufferMax)
+{
+ size_t read, write;
+
+ for(read = 0, write = 0;; read++)
+ {
+ if (read == bufferMax)
+ return FALSE;
+
+ if (L'\r' == buffer[read])
+ continue;
+
+ if (L'\n' == buffer[read] ||
+ L'\t' == buffer[read] ||
+ L'\b' == buffer[read])
+ {
+ buffer[write] = L' ';
+ }
+
+ buffer[write] = buffer[read];
+ if (L'\0' == buffer[read])
+ break;
+
+ write++;
+ }
+
+ return TRUE;
+}
+
+static HRESULT
+ListWidget_GetDeviceStatus(ifc_device *device, wchar_t *buffer, size_t bufferMax)
+{
+ HRESULT hr;
+ ifc_deviceactivity *activity;
+
+ if(NULL == buffer)
+ return E_POINTER;
+
+ buffer[0] = L'\0';
+ if (NULL == device)
+ return S_OK;
+
+ hr = device->GetActivity(&activity);
+ if (S_OK == hr && NULL != activity)
+ {
+ hr = activity->GetStatus(buffer, bufferMax);
+ if (FAILED(hr) || L'\0' == buffer[0])
+ hr = activity->GetDisplayName(buffer, bufferMax);
+
+ activity->Release();
+ }
+
+ if (FAILED(hr) || L'\0' == buffer[0])
+ hr = device->GetStatus(buffer, bufferMax);
+
+ if (E_NOTIMPL == hr)
+ {
+ hr = S_OK;
+ buffer[0] = L'\0';
+ }
+
+ return hr;
+
+}
+BOOL
+ListWidget_FormatItemCommandTip(ListWidget *self, ListWidgetItem *item, const RECT *commandRect, wchar_t *buffer, size_t bufferMax)
+{
+ size_t index;
+ RECT rect;
+
+ if (NULL == self)
+ return FALSE;
+
+ for(index = 0; index < self->commandsCount; index++)
+ {
+ ListWidgetCommand *command = self->commands[index];
+ if (FALSE != ListWidget_GetCommandRect(command, &rect) &&
+ FALSE != EqualRect(&rect, commandRect))
+ {
+ const wchar_t *value;
+ wchar_t *cursor;
+ size_t remaining;
+
+ cursor = buffer;
+ remaining = bufferMax;
+
+ value = ListWidget_GetCommandTitle(command);
+ if (FALSE == IS_STRING_EMPTY(value))
+ StringCchCopyEx(cursor, remaining, value, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+
+ value = ListWidget_GetCommandDescription(command);
+ if (FALSE == IS_STRING_EMPTY(value))
+ {
+ if (cursor != buffer)
+ StringCchCopyEx(cursor, remaining, L"\r\n", &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+ StringCchCopyEx(cursor, remaining, value, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+ }
+
+ return (cursor != buffer);
+ }
+ }
+
+ return FALSE;
+}
+
+BOOL
+ListWidget_FormatItemTip(ListWidget *self, ListWidgetItem *item, wchar_t *buffer, size_t bufferMax)
+{
+ ifc_device *device;
+ ifc_devicetype *type;
+ ifc_deviceconnection *connection;
+ wchar_t value[1024], valueName[512], *cursor;
+ size_t remaining;
+ uint64_t totalSpace, usedSpace;
+
+ if (NULL == item ||
+ NULL == WASABI_API_DEVICES ||
+ S_OK != WASABI_API_DEVICES->DeviceFind(item->name, &device))
+ {
+ return FALSE;
+ }
+
+ cursor = buffer;
+ remaining = bufferMax;
+
+ if (FALSE != ListWidgetItem_IsTextTruncated(item))
+ {
+ if (SUCCEEDED(device->GetDisplayName(value, ARRAYSIZE(value))) &&
+ L'\0' != value[0])
+ {
+ ListWidget_FilterItemTitle(value, ARRAYSIZE(value));
+ StringCchCopyEx(cursor, remaining, value, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+ }
+ }
+
+ if (SUCCEEDED(device->GetModel(value, ARRAYSIZE(value))) &&
+ L'\0' != value[0])
+ {
+ if (cursor != buffer)
+ StringCchCopyEx(cursor, remaining, L"\r\n", &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_DEVICE_MODEL_SHORT, valueName, ARRAYSIZE(valueName));
+ HRESULT hr;
+ if (L'\0' != valueName[0])
+ hr = StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE, L"%s: ", valueName);
+ else
+ hr = S_OK;
+
+ if (SUCCEEDED(hr))
+ StringCchCopyEx(cursor, remaining, value, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+ }
+
+ if (NULL != WASABI_API_DEVICES &&
+ S_OK == WASABI_API_DEVICES->TypeFind(device->GetType(), &type))
+ {
+ const char* typeStr = device->GetDisplayType();
+ if (typeStr && *typeStr)
+ {
+ if (cursor != buffer)
+ StringCchCopyEx(cursor, remaining, L"\r\n", &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_DEVICE_TYPE_SHORT, valueName, ARRAYSIZE(valueName));
+ if (L'\0' != valueName[0])
+ StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE, L"%s: ", valueName);
+
+ StringCchCopyEx(cursor, remaining, AutoWide(typeStr), &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+ }
+ else
+ {
+ if (SUCCEEDED(type->GetDisplayName(value, ARRAYSIZE(value))) &&
+ L'\0' != value[0])
+ {
+ if (cursor != buffer)
+ StringCchCopyEx(cursor, remaining, L"\r\n", &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_DEVICE_TYPE_SHORT, valueName, ARRAYSIZE(valueName));
+ if (L'\0' != valueName[0])
+ StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE, L"%s: ", valueName);
+
+ StringCchCopyEx(cursor, remaining, value, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+ }
+ }
+ type->Release();
+ }
+
+ if (NULL != WASABI_API_DEVICES &&
+ S_OK == WASABI_API_DEVICES->ConnectionFind(device->GetConnection(), &connection))
+ {
+ if (SUCCEEDED(connection->GetDisplayName(value, ARRAYSIZE(value))) &&
+ L'\0' != value[0])
+ {
+ if (cursor != buffer)
+ StringCchCopyEx(cursor, remaining, L"\r\n", &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_DEVICE_CONNECTION_SHORT, valueName, ARRAYSIZE(valueName));
+ if (L'\0' != valueName[0])
+ StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE, L"%s: ", valueName);
+
+ StringCchCopyEx(cursor, remaining, value, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+ }
+ connection->Release();
+ }
+
+ if (FAILED(device->GetTotalSpace(&totalSpace)) ||
+ 0 == totalSpace)
+ {
+ totalSpace = ((uint64_t)-1);
+ }
+
+ if (FAILED(device->GetUsedSpace(&usedSpace)))
+ usedSpace = ((uint64_t)-1);
+ else if (((uint64_t)-1) != totalSpace && usedSpace > totalSpace)
+ usedSpace = totalSpace;
+
+ if (((uint64_t)-1) != totalSpace && ((uint64_t)-1) != usedSpace)
+ {
+ if (NULL != WASABI_API_LNG->FormattedSizeString(value, ARRAYSIZE(value), totalSpace - usedSpace))
+ {
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_FREE_SPACE, valueName, ARRAYSIZE(valueName));
+ if (L'\0' != valueName[0])
+ {
+ if (cursor != buffer)
+ StringCchCopyEx(cursor, remaining, L"\r\n", &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+
+ StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE, L"%s: %s",
+ valueName, value);
+ }
+ }
+ }
+
+ if (((uint64_t)-1) != totalSpace)
+ {
+ if (NULL != WASABI_API_LNG->FormattedSizeString(value, ARRAYSIZE(value), totalSpace))
+ {
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_TOTAL_SPACE, valueName, ARRAYSIZE(valueName));
+ if (L'\0' != valueName[0])
+ {
+ if (cursor != buffer)
+ StringCchCopyEx(cursor, remaining, L"\r\n", &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+
+ StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE, L"%s: %s",
+ valueName, value);
+ }
+ }
+ }
+
+ // status
+ /*if (SUCCEEDED(ListWidget_GetDeviceStatus(device, value, ARRAYSIZE(value))) &&
+ L'\0' != value[0])
+ {
+ if (cursor != buffer)
+ StringCchCopyEx(cursor, remaining, L"\r\n", &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_DEVICE_STATUS_SHORT, valueName, ARRAYSIZE(valueName));
+ if (L'\0' != valueName[0])
+ hr = StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE, L"%s: ", valueName);
+ else
+ hr = S_OK;
+
+ if (SUCCEEDED(hr))
+ hr = StringCchCopyEx(cursor, remaining, value, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+ }
+ */
+
+ device->Release();
+ return (cursor != buffer);
+}
+
+BOOL
+ListWidget_FormatItemTitleTip(ListWidget *self, ListWidgetItem *item, wchar_t *buffer, size_t bufferMax)
+{
+ BOOL result;
+ ifc_device *device;
+
+ if (NULL == item ||
+ FALSE == ListWidgetItem_IsTextTruncated(item) ||
+ NULL == WASABI_API_DEVICES ||
+ S_OK != WASABI_API_DEVICES->DeviceFind(item->name, &device))
+ {
+ return FALSE;
+ }
+
+ if (SUCCEEDED(device->GetDisplayName(buffer, bufferMax)))
+ {
+ ListWidget_FilterItemTitle(buffer, bufferMax);
+ result = TRUE;
+ }
+ else
+ result = FALSE;
+
+ device->Release();
+
+ return result;
+}
+
+BOOL
+ListWidget_FormatItemSpaceTip(ListWidget *self, ListWidgetItem *item, wchar_t *buffer, size_t bufferMax)
+{
+ ifc_device *device;
+
+ wchar_t value[1024], valueName[512], *cursor;
+ size_t remaining;
+ uint64_t totalSpace, usedSpace;
+
+ if (NULL == item ||
+ NULL == WASABI_API_DEVICES ||
+ S_OK != WASABI_API_DEVICES->DeviceFind(item->name, &device))
+ {
+ return FALSE;
+ }
+
+ cursor = buffer;
+ remaining = bufferMax;
+
+ if (FAILED(device->GetTotalSpace(&totalSpace)) ||
+ 0 == totalSpace)
+ {
+ totalSpace = ((uint64_t)-1);
+ }
+
+ if (FAILED(device->GetUsedSpace(&usedSpace)))
+ usedSpace = ((uint64_t)-1);
+ else if (((uint64_t)-1) != totalSpace && usedSpace > totalSpace)
+ usedSpace = totalSpace;
+
+ if (((uint64_t)-1) != usedSpace)
+ {
+ if (NULL != WASABI_API_LNG->FormattedSizeString(value, ARRAYSIZE(value), usedSpace))
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_USED_SPACE, valueName, ARRAYSIZE(valueName));
+ if (L'\0' != valueName[0])
+ {
+ if (cursor != buffer)
+ StringCchCopyEx(cursor, remaining, L"\r\n", &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+
+ StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE, L"%s: %s",
+ valueName, value);
+ }
+ }
+ }
+
+ if (((uint64_t)-1) != totalSpace && ((uint64_t)-1) != usedSpace)
+ {
+ if (NULL != WASABI_API_LNG->FormattedSizeString(value, ARRAYSIZE(value), totalSpace - usedSpace))
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_FREE_SPACE, valueName, ARRAYSIZE(valueName));
+ if (L'\0' != valueName[0])
+ {
+ if (cursor != buffer)
+ StringCchCopyEx(cursor, remaining, L"\r\n", &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+
+ StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE, L"%s: %s",
+ valueName, value);
+ }
+ }
+ }
+
+ if (((uint64_t)-1) != totalSpace)
+ {
+ if (NULL != WASABI_API_LNG->FormattedSizeString(value, ARRAYSIZE(value), totalSpace))
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_TOTAL_SPACE, valueName, ARRAYSIZE(valueName));
+ if (L'\0' != valueName[0])
+ {
+ if (cursor != buffer)
+ StringCchCopyEx(cursor, remaining, L"\r\n", &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+
+ StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE, L"%s: %s",
+ valueName, value);
+ }
+ }
+ }
+
+ device->Release();
+ return (cursor != buffer);
+}
+
+BOOL
+ListWidget_FormatItemStatus(ListWidget *self, ListWidgetItem *item, wchar_t *buffer, size_t bufferMax)
+{
+ ifc_device *device;
+ ifc_devicetype *type;
+ ifc_deviceconnection *connection;
+
+ HRESULT hr;
+ wchar_t value[512], valueName[128], *cursor;
+ size_t remaining;
+
+ if (NULL == item ||
+ NULL == WASABI_API_DEVICES ||
+ S_OK != WASABI_API_DEVICES->DeviceFind(item->name, &device))
+ {
+ return FALSE;
+ }
+
+ hr = S_OK;
+ cursor = buffer;
+ remaining = bufferMax;
+
+ if (FALSE != ListWidgetItem_IsTextTruncated(item))
+ {
+ if (SUCCEEDED(device->GetDisplayName(value, ARRAYSIZE(value))) &&
+ L'\0' != value[0])
+ {
+ ListWidget_FilterItemTitle(value, ARRAYSIZE(value));
+ hr = StringCchCopyEx(cursor, remaining, value, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+ }
+ }
+
+ if (cursor == buffer &&
+ SUCCEEDED(device->GetModel(value, ARRAYSIZE(value))) &&
+ L'\0' != value[0])
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_DEVICE_MODEL_SHORT, valueName, ARRAYSIZE(valueName));
+ if (L'\0' != valueName[0])
+ hr = StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE, L"%s: ", valueName);
+
+ if (SUCCEEDED(hr))
+ hr = StringCchCopyEx(cursor, remaining, value, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+ }
+
+ if (cursor == buffer &&
+ NULL != WASABI_API_DEVICES &&
+ S_OK == WASABI_API_DEVICES->TypeFind(device->GetType(), &type))
+ {
+ if (SUCCEEDED(type->GetDisplayName(value, ARRAYSIZE(value))) &&
+ L'\0' != value[0])
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_DEVICE_TYPE_SHORT, valueName, ARRAYSIZE(valueName));
+ if (L'\0' != valueName[0])
+ hr = StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE, L"%s: ", valueName);
+
+ if (SUCCEEDED(hr))
+ hr = StringCchCopyEx(cursor, remaining, value, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+ }
+
+ type->Release();
+ }
+ else
+ {
+ const char* typeStr = device->GetDisplayType();
+ if (typeStr && *typeStr)
+ {
+ if (cursor != buffer)
+ hr = StringCchCopyEx(cursor, remaining, L", ", &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_DEVICE_TYPE_SHORT, valueName, ARRAYSIZE(valueName));
+ if (L'\0' != valueName[0])
+ hr = StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE, L"%s: ", valueName);
+
+ if (SUCCEEDED(hr))
+ StringCchCopyEx(cursor, remaining, AutoWide(typeStr), &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+ }
+ }
+
+ if (NULL != WASABI_API_DEVICES &&
+ S_OK == WASABI_API_DEVICES->ConnectionFind(device->GetConnection(), &connection))
+ {
+ if (SUCCEEDED(connection->GetDisplayName(value, ARRAYSIZE(value))) &&
+ L'\0' != value[0])
+ {
+ if (cursor != buffer)
+ StringCchCopyEx(cursor, remaining, L", ", &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_DEVICE_CONNECTION_SHORT, valueName, ARRAYSIZE(valueName));
+ if (L'\0' != valueName[0])
+ StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE, L"%s: ", valueName);
+
+ StringCchCopyEx(cursor, remaining, value, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+ }
+ connection->Release();
+ }
+
+ if (cursor == buffer &&
+ SUCCEEDED(device->GetDisplayName(value, ARRAYSIZE(value))))
+ {
+ ListWidget_FilterItemTitle(value, ARRAYSIZE(value));
+ StringCchCopyEx(cursor, remaining, value, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+ }
+
+ if (SUCCEEDED(ListWidget_GetDeviceStatus(device, value, ARRAYSIZE(value))) &&
+ L'\0' != value[0])
+ {
+ if (cursor != buffer)
+ StringCchCopyEx(cursor, remaining, L", ", &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+
+ StringCchCopyEx(cursor, remaining, value, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE);
+ }
+
+ device->Release();
+
+ return (cursor != buffer);
+}
+
+BOOL
+ListWidget_FormatItemSpaceStatus(ListWidget *self, ListWidgetItem *item, wchar_t *buffer, size_t bufferMax)
+{
+ ifc_device *device;
+
+ wchar_t *cursor;
+ size_t remaining;
+ uint64_t totalSpace, usedSpace;
+
+ if (NULL == item ||
+ NULL == WASABI_API_DEVICES ||
+ S_OK != WASABI_API_DEVICES->DeviceFind(item->name, &device))
+ {
+ return FALSE;
+ }
+
+ cursor = buffer;
+ remaining = bufferMax;
+
+ if (FAILED(device->GetTotalSpace(&totalSpace)) ||
+ 0 == totalSpace)
+ {
+ totalSpace = ((uint64_t)-1);
+ }
+
+ if (FAILED(device->GetUsedSpace(&usedSpace)))
+ usedSpace = ((uint64_t)-1);
+ else if (((uint64_t)-1) != totalSpace && usedSpace > totalSpace)
+ usedSpace = totalSpace;
+
+ if (((uint64_t)-1) != totalSpace)
+ {
+ if (((uint64_t)-1) != usedSpace)
+ {
+ wchar_t value1[64] = {0}, value2[64] = {0};
+ if (NULL != WASABI_API_LNG->FormattedSizeString(value1, ARRAYSIZE(value1), totalSpace - usedSpace) &&
+ NULL != WASABI_API_LNG->FormattedSizeString(value2, ARRAYSIZE(value2), totalSpace))
+ {
+ wchar_t format[128] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_STATUS_SPACE_TEMPLATE, format, ARRAYSIZE(format));
+ StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE,
+ format, value1, value2);
+ }
+ }
+ else
+ {
+ wchar_t value[64] = {0};
+ if (NULL != WASABI_API_LNG->FormattedSizeString(value, ARRAYSIZE(value), totalSpace))
+ {
+ wchar_t valueName[128] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_TOTAL_SPACE, valueName, ARRAYSIZE(valueName));
+ if (L'\0' != valueName[0])
+ {
+ StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE,
+ L"%s %s", value, valueName);
+
+ }
+ }
+ }
+ }
+
+ device->Release();
+
+ return (cursor != buffer);
+}
+
+static void CALLBACK
+ListWidget_EndTitleEditCb(HWND editorWindow, BOOL canceled, const wchar_t *text, void *user)
+{
+ HWND hwnd;
+ ListWidget *self;
+ char *itemName;
+
+ hwnd = GetAncestor(editorWindow, GA_PARENT);
+ self = WIDGET_GET_SELF(hwnd, ListWidget);
+
+ itemName = (char*)user;
+
+ if (NULL != self)
+ {
+ ListWidgetItem *item;
+
+ if (self->titleEditor == editorWindow)
+ self->titleEditor = NULL;
+
+ item = ListWidget_FindItem(self, itemName, NULL, NULL);
+ if (NULL != item)
+ {
+ ListWidgetItemMetric metrics;
+ WidgetStyle *style;
+ POINT origin;
+ RECT rect;
+
+ ListWidgetItem_UnsetTextEdited(item);
+
+ style = WIDGET_GET_STYLE(hwnd);
+
+ if (NULL != style &&
+ FALSE != ListWidget_GetItemMetrics(style, &metrics) &&
+ FALSE != ListWidget_GetItemTitleRect(self, item, &metrics, FALSE, &rect))
+ {
+ if (FALSE != ListWidget_GetViewOrigin(hwnd, &origin))
+ OffsetRect(&rect, origin.x, origin.y);
+
+ InvalidateRect(hwnd, &rect, FALSE);
+ }
+ }
+
+ if (FALSE == canceled)
+ {
+ ifc_device *device;
+ if (NULL != WASABI_API_DEVICES &&
+ S_OK == WASABI_API_DEVICES->DeviceFind(itemName, &device))
+ {
+ wchar_t buffer[1024] = {0};
+
+ if (FAILED(device->GetDisplayName(buffer, ARRAYSIZE(buffer))) ||
+ CSTR_EQUAL != CompareString(LOCALE_USER_DEFAULT, 0, buffer, -1, text, -1))
+ {
+ HRESULT hr;
+
+ hr = device->SetDisplayName(text);
+
+ if (FAILED(hr))
+ {
+ wchar_t title[256] = {0}, message[1024] = {0};
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_MESSAGEBOX_TITLE, title, ARRAYSIZE(title));
+ WASABI_API_LNGSTRINGW_BUF(IDS_MESSAGE_UNABLE_TO_RENAME, message, ARRAYSIZE(message));
+
+ MessageBox(hwnd, message, title, MB_OK | MB_ICONERROR);
+ }
+ }
+
+ device->Release();
+ }
+ }
+ }
+
+ EMBEDDEDEDITOR_SET_USER_DATA(editorWindow, NULL);
+ AnsiString_Free(itemName);
+}
+
+HWND
+ListWidget_BeginItemTitleEdit(ListWidget *self, HWND hwnd, ListWidgetItem *item)
+{
+ RECT rect;
+ WidgetStyle *style;
+ ListWidgetItemMetric metrics;
+ HWND editor;
+ POINT origin;
+ unsigned long editorStyleEx, editorStyle;
+ char *itemName;
+ ifc_device * device;
+ BOOL blockEditor;
+
+
+ if (NULL == self || NULL == item)
+ return NULL;
+
+ style = WIDGET_GET_STYLE(hwnd);
+ if (NULL == style)
+ return NULL;
+
+ if (NULL != WASABI_API_DEVICES &&
+ S_OK == WASABI_API_DEVICES->DeviceFind(item->name, &device))
+ {
+ blockEditor = (FALSE == DeviceCommand_GetEnabled(device, "rename",
+ DeviceCommandContext_ViewMenu));
+ device->Release();
+ }
+ else
+ blockEditor = TRUE;
+
+ if (FALSE != blockEditor)
+ return NULL;
+
+ if (FALSE == ListWidget_GetItemMetrics(style, &metrics))
+ return NULL;
+
+ if (FALSE == ListWidget_GetItemTitleRect(self, item, &metrics, FALSE, &rect))
+ return NULL;
+
+ if (FALSE != ListWidget_GetViewOrigin(hwnd, &origin))
+ OffsetRect(&rect, origin.x, origin.y);
+
+ editorStyleEx = WS_EX_CLIENTEDGE;
+ editorStyle = WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN |
+ ES_CENTER | ES_NOHIDESEL | ES_MULTILINE | ES_AUTOVSCROLL;
+
+ EmbeddedEditor_AdjustWindowRectEx(&rect, editorStyleEx, editorStyle);
+
+ editor = CreateWindowEx(editorStyleEx, WC_EDIT, item->title, editorStyle,
+ rect.left, rect.top, 0, 0,
+ hwnd, NULL, NULL, 0L);
+ if (NULL == editor)
+ return NULL;
+
+
+ itemName = AnsiString_Duplicate(item->name);
+ if (FALSE == EmbeddedEditor_Attach(editor, ListWidget_EndTitleEditCb, itemName))
+ {
+ AnsiString_Free(itemName);
+ DestroyWindow(editor);
+ return NULL;
+ }
+
+ ListWidgetItem_SetTextEdited(item);
+
+ EMBEDDEDEDITOR_SET_ANCHOR_POINT(editor, rect.left, rect.top);
+ EMBEDDEDEDITOR_SET_MAX_SIZE(editor, RECTWIDTH(rect), 0);
+
+
+ SendMessage(editor, WM_SETFONT, (WPARAM)WIDGETSTYLE_TEXT_FONT(style), 0L);
+
+ ListWidget_UpdateTitleEditorColors(editor, style);
+
+ SendMessage(editor, EM_SETSEL, 0, -1);
+ ShowWindow(editor, SW_SHOW);
+ SetFocus(editor);
+
+ return editor;
+}
+
+
+int
+ListWidget_CompareItemPos(ListWidget *self, ListWidgetItem *item1, ListWidgetItem *item2)
+{
+ size_t iCategory1, iGroup1, iItem1;
+ size_t iCategory2, iGroup2, iItem2;
+
+ if (FALSE == ListWidget_FindItemPos(self, item1, &iCategory1, &iGroup1, &iItem1) ||
+ FALSE == ListWidget_FindItemPos(self, item2, &iCategory2, &iGroup2, &iItem2))
+ {
+ return _NLSCMPERROR;
+ }
+
+ if (iCategory1 != iCategory2)
+ return (int)(iCategory1 - iCategory2);
+
+ if (iGroup1 != iGroup2)
+ return (int)(iGroup1 - iGroup2);
+
+ return (int)(iItem1 - iItem2);
+}
+
+BOOL
+ListWidget_GetViewItemPos(HWND hwnd, ListWidgetItem *item, POINT *pt)
+{
+ if (NULL == hwnd ||
+ NULL == item ||
+ NULL == pt)
+ {
+ return FALSE;
+ }
+
+ if (FALSE == ListWidget_GetViewOrigin(hwnd, pt))
+ {
+ pt->x = 0;
+ pt->y = 0;
+ }
+
+ pt->x = item->rect.left - pt->x;
+ pt->y = item->rect.top - pt->y;
+
+ return TRUE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/listWidgetPaint.cpp b/Src/Plugins/Library/ml_devices/listWidgetPaint.cpp
new file mode 100644
index 00000000..9f281d43
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/listWidgetPaint.cpp
@@ -0,0 +1,725 @@
+#include "main.h"
+#include "./listWidgetInternal.h"
+
+#include <strsafe.h>
+
+static BOOL
+ListWidgetPaintSpacebar_Initialize(ListWidgetPaintSpacebar *self, ListWidget *widget,
+ WidgetStyle *style, HWND hwnd, long width, long height)
+{
+ if (NULL == self)
+ return FALSE;
+
+ self->bitmap = ListWidget_GetSpacebarBitmap(widget, style, hwnd, width, height);
+ if (NULL == self->bitmap)
+ {
+ self->width = 0;
+ self->height = 0;
+ self->emptyBarOffset = 0;
+ self->filledBarOffset = 0;
+ return FALSE;
+ }
+
+ self->width = width;
+ self->height = height;
+ self->emptyBarOffset = 0;
+ self->filledBarOffset = height;
+ return TRUE;
+}
+
+static void
+ListWidgetPaintSpacebar_Uninitialize(ListWidgetPaintSpacebar *self)
+{
+ if (NULL == self)
+ return;
+}
+
+static BOOL
+ListWidgetPaintArrow_Initialize(ListWidgetPaintArrow *self, ListWidget *widget,
+ WidgetStyle *style, HWND hwnd)
+{
+ BITMAP bitmapInfo;
+
+ if (NULL == self)
+ return FALSE;
+
+ self->bitmap = ListWidget_GetArrowsBitmap(widget, style, hwnd);
+ if (NULL == self->bitmap ||
+ sizeof(bitmapInfo) != GetObject(self->bitmap, sizeof(bitmapInfo), &bitmapInfo))
+ {
+ ZeroMemory(self, sizeof(ListWidgetPaintArrow));
+ return FALSE;
+ }
+
+ self->width = bitmapInfo.bmWidth;
+ self->height = bitmapInfo.bmHeight/2;
+ if (self->height < 0)
+ self->height = -self->height;
+
+ self->collapsedOffset = 0;
+ self->expandedOffset = self->height;
+
+ return TRUE;
+}
+
+static void
+ListWidgetPaintArrow_Uninitialize(ListWidgetPaintArrow *self)
+{
+ if (NULL == self)
+ return;
+}
+
+BOOL
+ListWidgetPaint_Initialize(ListWidgetPaint *self, ListWidget *widget, WidgetStyle *style,
+ HWND hwnd, HDC hdc, const RECT *paintRect, BOOL erase)
+{
+ HDC windowDC;
+
+ if (NULL == self)
+ return FALSE;
+
+ if (NULL == widget || NULL == style)
+ return FALSE;
+
+ self->widget = widget;
+ self->style = style;
+ self->hwnd = hwnd;
+ self->hdc = hdc;
+ self->erase = erase;
+ self->paintRect = paintRect;
+
+ windowDC = GetDCEx(hwnd, NULL, DCX_CACHE | DCX_NORESETATTRS);
+ if (NULL != windowDC)
+ {
+ self->sourceDC = CreateCompatibleDC(windowDC);
+ ReleaseDC(hwnd, windowDC);
+ }
+
+ if (NULL == self->sourceDC)
+ return FALSE;
+
+ if (FALSE == ListWidget_GetItemMetrics(style, &self->itemMetrics))
+ return FALSE;
+
+ if (FALSE == ListWidget_GetCategoryMetrics(style, &self->categoryMetrics))
+ return FALSE;
+
+ if (FALSE == ListWidgetPaintSpacebar_Initialize(&self->spacebar, widget, style, hwnd,
+ widget->itemWidth - (self->itemMetrics.offsetLeft + self->itemMetrics.offsetRight),
+ self->itemMetrics.spacebarHeight))
+ {
+ // ListWidgetPaint_Uninitialize(self);
+ // return FALSE;
+ }
+
+
+ if (FALSE == ListWidgetPaintArrow_Initialize(&self->arrow, widget, style, hwnd))
+ {
+ // ListWidgetPaint_Uninitialize(self);
+ // return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+ListWidgetPaint_Uninitialize(ListWidgetPaint *self)
+{
+ if (NULL == self)
+ return;
+
+ if (NULL != self->sourceDC)
+ DeleteDC(self->sourceDC);
+
+ ListWidgetPaintSpacebar_Uninitialize(&self->spacebar);
+ ListWidgetPaintArrow_Uninitialize(&self->arrow);
+}
+
+
+static BOOL
+ListWidgetPaint_DrawSpacebar(ListWidgetPaint *self, HDC hdc, int x, int y,
+ uint64_t totalSpace, uint64_t usedSpace)
+{
+ RECT *partRect, sourceRect;
+ BOOL succeeded;
+ long usedWidth;
+ ListWidgetPaintSpacebar *spacebar;
+
+ if (NULL == self)
+ return FALSE;
+
+ partRect = &self->partRect;
+ spacebar = &self->spacebar;
+ if (NULL == spacebar->bitmap)
+ return FALSE;
+
+ succeeded = TRUE;
+
+ if (usedSpace > totalSpace)
+ usedSpace = totalSpace;
+
+ if (0 == totalSpace)
+ usedWidth = 0;
+ else
+ usedWidth = (long)(spacebar->width * (double)usedSpace/totalSpace);
+
+
+ SetRect(partRect, x, y, x + spacebar->width, y + spacebar->height);
+ SetRect(&sourceRect, 0, 0, spacebar->width, spacebar->height);
+ OffsetRect(&sourceRect, 0, spacebar->emptyBarOffset);
+
+ if (FALSE == Image_AlphaBlend(hdc, partRect, self->sourceDC, &sourceRect, 255,
+ spacebar->bitmap, self->paintRect, AlphaBlend_Normal, NULL))
+ {
+ succeeded = FALSE;
+ }
+
+ if (0 != usedWidth)
+ {
+ SetRect(partRect, x, y, x + usedWidth, y + spacebar->height);
+ SetRect(&sourceRect, 0, 0, usedWidth, spacebar->height);
+ OffsetRect(&sourceRect, 0, spacebar->filledBarOffset);
+
+ if (FALSE == Image_AlphaBlend(hdc, partRect, self->sourceDC, &sourceRect, 255,
+ spacebar->bitmap, self->paintRect, AlphaBlend_Normal, NULL))
+ {
+ succeeded = FALSE;
+ }
+ }
+
+ return succeeded;
+}
+
+void
+ListWidgetPaint_DrawItemAction(ListWidgetPaint *self, HDC hdc, ListWidgetItem *item, ListWidgetActivity *activity)
+{
+ HDC sourceDC;
+ WidgetStyle *style;
+ ListWidget *widget;
+ ListWidgetItemMetric *metrics;
+ RECT *partRect, sourceRect;
+ HBITMAP bitmap, prevSourceBitmap;
+ COLORREF prevTextColor;
+ HFONT prevFont;
+ int stringLength;
+
+ if (NULL == activity ||
+ FALSE == ListWidget_GetItemActivityRect(self->widget, item, &self->itemMetrics, &self->partRect) ||
+ FALSE == IntersectRect(&sourceRect, &self->partRect, self->paintRect))
+ {
+ return;
+ }
+
+
+ style = self->style;
+ widget = self->widget;
+ metrics = &self->itemMetrics;
+ sourceDC = self->sourceDC;
+ partRect = &self->partRect;
+ prevSourceBitmap = GetCurrentBitmap(sourceDC);
+ prevTextColor = GetTextColor(hdc);
+ prevFont = GetCurrentFont(hdc);
+
+ if (NULL != widget->activityFont)
+ SelectFont(hdc, widget->activityFont);
+
+ bitmap = ListWidget_GetActivityBadgeBitmap(widget, style, self->hwnd,
+ RECTWIDTH(*partRect), RECTHEIGHT(*partRect));
+ if (NULL != bitmap)
+ {
+ Image_AlphaBlend(hdc, partRect, sourceDC, NULL, 255, bitmap, self->paintRect,
+ AlphaBlend_Normal, NULL);
+ }
+
+
+ if (FALSE == IS_STRING_EMPTY(activity->title) &&
+ FALSE != ListWidget_GetItemActivityTitleRect(widget, hdc, item, metrics, partRect) &&
+ FALSE != IntersectRect(&sourceRect, partRect, self->paintRect))
+ {
+ stringLength = lstrlen(activity->title);
+
+ OffsetRect(partRect, 0, 1);
+ SetTextColor(hdc, 0x000000);
+ DrawText(hdc, activity->title, stringLength, partRect,
+ DT_LEFT | DT_NOPREFIX | DT_WORDBREAK | DT_EDITCONTROL | DT_WORD_ELLIPSIS);
+
+ OffsetRect(partRect, 0, -1);
+ SetTextColor(hdc, 0xFFFFFF);
+ DrawText(hdc, activity->title, stringLength, partRect,
+ DT_LEFT | DT_NOPREFIX | DT_WORDBREAK | DT_EDITCONTROL | DT_WORD_ELLIPSIS);
+
+ }
+
+ if (FALSE != ListWidget_GetItemActivityProgressRect(widget, hdc, item, metrics, partRect) &&
+ FALSE != IntersectRect(&sourceRect, partRect, self->paintRect))
+ {
+ bitmap = ListWidget_GetActivityProgressBitmap(widget, style);
+ if (NULL != bitmap)
+ {
+ SetRect(&sourceRect, 0, 0,
+ widget->activityMetrics.progressWidth, widget->activityMetrics.progressHeight);
+ OffsetRect(&sourceRect, 0, widget->activityMetrics.progressHeight * activity->step);
+
+ Image_AlphaBlend(hdc, partRect, sourceDC, &sourceRect, 255, bitmap, self->paintRect,
+ AlphaBlend_Normal, NULL);
+ }
+ }
+
+ if ((unsigned int)-1 != activity->percent &&
+ FALSE != ListWidget_GetItemActivityPercentRect(widget, hdc, item, metrics, partRect) &&
+ FALSE != IntersectRect(&sourceRect, partRect, self->paintRect))
+ {
+ wchar_t buffer[6] = {0};
+
+ if (FAILED(StringCchPrintf(buffer, ARRAYSIZE(buffer), L"%d%%", activity->percent)))
+ stringLength = 0;
+ else
+ stringLength = lstrlen(buffer);
+
+ OffsetRect(partRect, 0, 1);
+ SetTextColor(hdc, 0x000000);
+ DrawText(hdc, buffer, stringLength, partRect,
+ DT_CENTER | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE);
+
+ OffsetRect(partRect, 0, -1);
+ SetTextColor(hdc, 0xFFFFFF);
+ DrawText(hdc, buffer, stringLength, partRect,
+ DT_CENTER | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE);
+ }
+
+ SetTextColor(hdc, prevTextColor);
+ SelectFont(hdc, prevFont);
+}
+
+BOOL
+ListWidgetPaint_DrawItem(ListWidgetPaint *self, ListWidgetItem *item)
+{
+ RECT frameRect;
+ long frameHeight, frameWidth;
+ HDC hdc, sourceDC, bufferDC, targetDC;
+ WidgetStyle *style;
+ ListWidget *widget;
+ ListWidgetItemMetric *metrics;
+ RECT *partRect, paintRect;
+ HBITMAP bitmap, prevSourceBitmap;
+
+ if (NULL == self || NULL == item)
+ return FALSE;
+
+ hdc = self->hdc;
+ style = self->style;
+ widget = self->widget;
+ metrics = &self->itemMetrics;
+ sourceDC = self->sourceDC;
+ partRect = &self->partRect;
+ prevSourceBitmap = GetCurrentBitmap(sourceDC);
+
+
+
+ if (FALSE == ListWidget_GetItemFrameRect(widget, item, metrics, &frameRect))
+ return FALSE;
+
+ frameWidth = RECTWIDTH(frameRect);
+ frameHeight = RECTHEIGHT(frameRect);
+
+ if (FALSE == IntersectRect(&paintRect, &item->rect, self->paintRect))
+ return TRUE;
+
+ CopyRect(partRect, &paintRect);
+
+ if (FALSE != BackBuffer_EnsureSizeEx(&widget->backBuffer,
+ RECTWIDTH(paintRect), RECTHEIGHT(paintRect),
+ RECTWIDTH(item->rect), RECTHEIGHT(item->rect)))
+ {
+
+ bufferDC = BackBuffer_GetDC(&widget->backBuffer);
+ if (NULL != bufferDC)
+ {
+ SetViewportOrgEx(bufferDC, -paintRect.left, -paintRect.top, NULL);
+ SetTextColor(bufferDC, GetTextColor(hdc));
+ SetBkColor(bufferDC, GetBkColor(hdc));
+ SetBkMode(bufferDC, GetBkMode(hdc));
+ SelectFont(bufferDC, GetCurrentFont(hdc));
+
+ targetDC = hdc;
+ hdc = bufferDC;
+ }
+ }
+ else
+ bufferDC = NULL;
+
+
+ if (FALSE != self->erase)
+ {
+ FillRect(hdc, partRect, style->backBrush);
+ }
+
+
+ if (FALSE != ListWidgetItem_IsHovered(item))
+ {
+ bitmap = ListWidget_GetHoverBitmap(widget, style, self->hwnd,
+ frameWidth, frameHeight);
+
+ Image_AlphaBlend(hdc, &frameRect, sourceDC, NULL, 255, bitmap, &paintRect,
+ AlphaBlend_Normal, NULL);
+ }
+
+ if (FALSE != ListWidgetItem_IsSelected(item))
+ {
+ bitmap = (GetFocus() == self->hwnd) ?
+ ListWidget_GetSelectBitmap(widget, style, self->hwnd, frameWidth, frameHeight) :
+ ListWidget_GetInactiveSelectBitmap(widget, style, self->hwnd, frameWidth, frameHeight);
+
+ Image_AlphaBlend(hdc, &frameRect, sourceDC, NULL, 255, bitmap, &paintRect,
+ AlphaBlend_Normal, NULL);
+ }
+
+
+ bitmap = ListWidget_GetItemImage(widget, style, item);
+ if (NULL != bitmap)
+ {
+ partRect->left = frameRect.left + metrics->offsetLeft + metrics->imageOffsetLeft;
+ partRect->top = frameRect.top + metrics->offsetTop + metrics->imageOffsetTop;
+ partRect->right = frameRect.right - (metrics->offsetRight + metrics->imageOffsetRight);
+ partRect->bottom = frameRect.bottom - metrics->imageOffsetBottom;
+
+ Image_AlphaBlend(hdc, partRect, sourceDC, NULL, 255, bitmap, &paintRect,
+ AlphaBlend_ScaleSource | AlphaBlend_AlignBottom, NULL);
+ }
+
+ bitmap = ListWidget_GetConnectionImage(style, item->connection,
+ widget->connectionSize.cx, widget->connectionSize.cy);
+ if (NULL != bitmap &&
+ FALSE != ListWidget_GetItemConnectionRect(widget, item, metrics, partRect))
+ {
+
+ Image_AlphaBlend(hdc, partRect, sourceDC, NULL, 255, bitmap, &paintRect,
+ AlphaBlend_AlignCenter | AlphaBlend_AlignBottom, NULL);
+ }
+
+ ListWidgetPaint_DrawItemAction(self, hdc, item, item->activity);
+
+ if (FALSE != ListWidgetItem_IsInteractive(item) && 0 != widget->commandsCount)
+ {
+
+ HBITMAP commandBitmap;
+ size_t index;
+ RECT commandPaintRect;
+
+ bitmap = NULL;
+ for(index = 0; index < widget->commandsCount; index++)
+ {
+ long offset;
+ BYTE sourceAlpha;
+ ListWidget_GetCommandRect(widget->commands[index], partRect);
+ OffsetRect(partRect, frameRect.left, frameRect.top);
+
+ if (FALSE == IntersectRect(&commandPaintRect, partRect, &paintRect))
+ continue;
+
+ if (FALSE != ListWidget_GetCommandPressed(widget->commands[index]))
+ sourceAlpha = 200;
+ else if (FALSE != ListWidget_GetCommandDisabled(widget->commands[index]))
+ sourceAlpha = 32;
+ else
+ sourceAlpha = 255;
+
+ if (NULL == item->activity &&
+ FALSE != ListWidget_GetCommandPrimary(widget->commands[index]))
+ {
+ bitmap = ListWidget_GetLargeBadgeBitmap(widget, style, self->hwnd,
+ RECTWIDTH(*partRect), RECTHEIGHT(*partRect));
+
+ Image_AlphaBlend(hdc, partRect, sourceDC, NULL, 255, bitmap, &commandPaintRect,
+ AlphaBlend_AlignCenter, NULL);
+
+ bitmap = NULL;
+
+ offset = widget->primaryCommandSize.cy/6;
+ if (offset < 3)
+ offset = 3;
+
+ InflateRect(partRect, -offset, -offset);
+ commandBitmap = ListWidget_GetCommandLargeBitmap(style, widget->commands[index],
+ RECTWIDTH(*partRect), RECTHEIGHT(*partRect));
+ if (NULL == commandBitmap)
+ commandBitmap = ListWidget_GetUnknownCommandLargeBitmap(widget, style,
+ RECTWIDTH(*partRect), RECTHEIGHT(*partRect));
+ }
+ else
+ {
+ if (NULL == bitmap)
+ {
+ bitmap = ListWidget_GetSmallBadgeBitmap(widget, style, self->hwnd,
+ RECTWIDTH(*partRect), RECTHEIGHT(*partRect));
+ }
+
+ Image_AlphaBlend(hdc, partRect, sourceDC, NULL, 255, bitmap, &commandPaintRect,
+ AlphaBlend_AlignCenter, NULL);
+
+ offset = widget->secondaryCommandSize.cy/6;
+ if (offset < 3)
+ offset = 3;
+
+ InflateRect(partRect, -offset, -offset);
+ commandBitmap = ListWidget_GetCommandSmallBitmap(style, widget->commands[index],
+ RECTWIDTH(*partRect), RECTHEIGHT(*partRect));
+
+ if (NULL == commandBitmap)
+ commandBitmap = ListWidget_GetUnknownCommandSmallBitmap(widget, style,
+ RECTWIDTH(*partRect), RECTHEIGHT(*partRect));
+ }
+
+ if (NULL != commandBitmap)
+ {
+ Image_AlphaBlend(hdc, partRect, sourceDC, NULL, sourceAlpha, commandBitmap, &commandPaintRect,
+ AlphaBlend_Normal, NULL);
+ }
+
+ }
+
+ }
+
+ if (FALSE != ListWidget_GetItemSpacebarRect(widget, item, metrics, partRect))
+ {
+ ListWidgetPaint_DrawSpacebar(self, hdc,
+ partRect->left,
+ partRect->top,
+ item->spaceTotal, item->spaceUsed);
+ }
+
+
+ if (FALSE == ListWidgetItem_IsTextEdited(item) &&
+ FALSE == IS_STRING_EMPTY(item->title) &&
+ FALSE != ListWidget_GetItemTitleRect(widget, item, metrics, TRUE, partRect))
+ {
+ DrawText(hdc, item->title, -1, partRect,
+ DT_CENTER | DT_NOPREFIX | DT_WORDBREAK | DT_EDITCONTROL | DT_END_ELLIPSIS);
+ }
+
+ SelectBitmap(sourceDC, prevSourceBitmap);
+
+ if (NULL != bufferDC)
+ {
+ hdc = targetDC;
+
+ SetViewportOrgEx(bufferDC, 0, 0, NULL);
+
+ BackBuffer_Copy(&widget->backBuffer, hdc,
+ paintRect.left, paintRect.top, RECTWIDTH(paintRect), RECTHEIGHT(paintRect));
+ }
+
+ return TRUE;
+}
+
+BOOL
+ListWidgetPaint_DrawCategory(ListWidgetPaint *self, ListWidgetCategory *category)
+{
+ HDC hdc;
+ RECT elementRect, *partRect;
+ WidgetStyle *style;
+ ListWidgetCategoryMetric *metrics;
+
+ if (NULL == self || NULL == category)
+ return FALSE;
+
+ hdc = self->hdc;
+ style = self->style;
+ partRect = &self->partRect;
+ metrics = &self->categoryMetrics;
+
+ if (FALSE == IntersectRect(partRect, &category->rect, self->paintRect))
+ return TRUE;
+
+ CopyRect(partRect, &category->rect);
+ partRect->right -= metrics->offsetRight;
+ if (FALSE != IntersectRect(partRect, partRect, self->paintRect))
+ FillRect(hdc, partRect, WIDGETSTYLE_CATEGORY_BRUSH(style));
+
+ partRect->left = category->rect.right - metrics->offsetRight;
+ partRect->right = category->rect.right;
+ if (FALSE != IntersectRect(partRect, partRect, self->paintRect))
+ FillRect(hdc, partRect, WIDGETSTYLE_BACK_BRUSH(style));
+
+ if (NULL != self->arrow.bitmap)
+ {
+ long limit;
+ SetRect(&elementRect, 0, 0, self->arrow.width, self->arrow.height);
+ OffsetRect(&elementRect,
+ 0,
+ (FALSE == category->collapsed) ?
+ self->arrow.expandedOffset :
+ self->arrow.collapsedOffset);
+
+
+ CopyRect(partRect, &category->rect);
+ partRect->left += metrics->offsetLeft;
+ partRect->right = partRect->left + self->arrow.width;
+
+ limit = (RECTHEIGHT(category->rect) - self->arrow.height - metrics->offsetTop);
+ partRect->top += metrics->offsetTop + limit/2 + limit%2;
+ partRect->bottom = partRect->top + self->arrow.height;
+
+ limit = category->rect.bottom - metrics->offsetBottom - metrics->lineHeight;
+ if (partRect->bottom > limit)
+ OffsetRect(partRect, 0, (limit - partRect->bottom));
+
+ limit = category->rect.top + metrics->offsetTop;
+ if (partRect->top < limit)
+ OffsetRect(partRect, 0, (limit - partRect->top));
+
+ Image_AlphaBlend(hdc, partRect, self->sourceDC, &elementRect,
+ 255, self->arrow.bitmap, self->paintRect,
+ AlphaBlend_Normal, NULL);
+ }
+
+ CopyRect(&elementRect, &category->rect);
+ elementRect.left += metrics->offsetLeft + metrics->iconWidth + metrics->titleOffsetLeft;
+ elementRect.top += metrics->offsetTop;
+ elementRect.right -= metrics->offsetRight;
+ elementRect.bottom -= (metrics->offsetBottom + metrics->lineHeight + metrics->lineOffsetTop);
+
+ if (FALSE != IntersectRect(partRect, &elementRect, self->paintRect))
+ {
+ COLORREF prevTextColor = SetTextColor(hdc, WIDGETSTYLE_CATEGORY_TEXT_COLOR(style));
+ HFONT prevFont = SelectFont(hdc, WIDGETSTYLE_CATEGORY_FONT(style));
+
+ if (NULL == category->countString)
+ {
+ size_t count, index;
+ wchar_t buffer[64] = {0};
+
+ count = 0;
+ index = category->groups.size();
+ while(index--)
+ {
+ count += category->groups[index]->items.size();
+ }
+
+ if (SUCCEEDED(StringCchPrintf(buffer, ARRAYSIZE(buffer), L" (%u)", count)))
+ category->countString = String_Duplicate(buffer);
+ }
+
+ if (-1 == category->titleWidth)
+ {
+ category->titleWidth = 0;
+
+ if (FALSE == IS_STRING_EMPTY(category->title))
+ {
+ SetRect(partRect, 0, 0, 0, RECTHEIGHT(elementRect));
+ if (FALSE != DrawText(hdc, category->title, -1, partRect,
+ DT_CALCRECT | DT_LEFT | DT_TOP | DT_NOPREFIX | DT_SINGLELINE))
+ {
+ category->titleWidth = RECTWIDTH(*partRect);
+ }
+ }
+ }
+
+ if (-1 == category->countWidth)
+ {
+ category->countWidth = 0;
+ if (FALSE == IS_STRING_EMPTY(category->countString))
+ {
+ SetRect(partRect, 0, 0, 0, RECTHEIGHT(elementRect));
+ if (FALSE != DrawText(hdc, category->countString, -1, partRect,
+ DT_CALCRECT | DT_LEFT | DT_TOP | DT_NOPREFIX | DT_SINGLELINE))
+ {
+ category->countWidth = RECTWIDTH(*partRect);
+ }
+ }
+ }
+
+ if (0 != category->titleWidth)
+ {
+ CopyRect(partRect, &elementRect);
+
+ if (partRect->right < (partRect->left + category->titleWidth + category->countWidth))
+ partRect->right = partRect->right - category->countWidth;
+
+ if (partRect->right > partRect->left)
+ {
+ DrawText(hdc, category->title, -1, partRect,
+ DT_LEFT | DT_BOTTOM | DT_NOPREFIX | DT_SINGLELINE | DT_END_ELLIPSIS);
+ }
+ }
+
+ if (0 != category->countWidth)
+ {
+ CopyRect(partRect, &elementRect);
+ partRect->left += category->titleWidth;
+
+ if (partRect->left > (partRect->right - category->countWidth))
+ {
+ partRect->left = partRect->right - category->countWidth;
+ if (partRect->left < elementRect.left)
+ partRect->left = elementRect.left;
+ }
+
+ if (partRect->right > partRect->left)
+ {
+ DrawText(hdc, category->countString, -1, partRect,
+ DT_LEFT | DT_BOTTOM | DT_NOPREFIX | DT_SINGLELINE);
+ }
+ }
+
+ SetTextColor(hdc, prevTextColor);
+ SelectFont(hdc, prevFont);
+ }
+
+ if (0 != metrics->lineHeight)
+ {
+ CopyRect(partRect, &category->rect);
+ partRect->right -= metrics->offsetRight;
+ partRect->bottom -= metrics->offsetBottom;
+ partRect->top = partRect->bottom - metrics->lineHeight;
+
+ if (FALSE != IntersectRect(partRect, partRect, self->paintRect))
+ {
+ COLORREF prevBackColor;
+
+ prevBackColor = SetBkColor(hdc, WIDGETSTYLE_CATEGORY_LINE_COLOR(style));
+ ExtTextOut(hdc, 0, 0, ETO_OPAQUE, partRect, NULL, 0, NULL);
+
+ SetBkColor(hdc, prevBackColor);
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL
+ListWidgetPaint_DrawEmptyCategoryText(ListWidgetPaint *self, ListWidgetCategory *category)
+{
+ HDC hdc;
+ WidgetStyle *style;
+ RECT *partRect;
+ BOOL result;
+ COLORREF prevTextColor;
+
+ if (NULL == self || NULL == category)
+ return FALSE;
+
+ hdc = self->hdc;
+ style = self->style;
+ partRect = &self->partRect;
+
+
+ if (FALSE == IntersectRect(partRect, self->paintRect, &category->emptyTextRect))
+ return TRUE;
+
+ if (FALSE != self->erase)
+ {
+ FillRect(hdc, partRect, style->backBrush);
+ }
+
+ if (FALSE != IS_STRING_EMPTY(category->emptyText))
+ return TRUE;
+
+ prevTextColor = SetTextColor(hdc, WIDGETSTYLE_CATEGORY_EMPTY_TEXT_COLOR(style));
+
+ result = DrawText(hdc, category->emptyText, -1, &category->emptyTextRect,
+ DT_CENTER | DT_TOP | DT_NOPREFIX | DT_WORDBREAK | DT_EDITCONTROL);
+
+ SetTextColor(hdc, prevTextColor);
+ return result;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/listWidgetTooltip.cpp b/Src/Plugins/Library/ml_devices/listWidgetTooltip.cpp
new file mode 100644
index 00000000..0bdb8dc4
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/listWidgetTooltip.cpp
@@ -0,0 +1,592 @@
+#include "main.h"
+#include "./listWidgetInternal.h"
+
+#include <strsafe.h>
+
+#define TOOLTIP_MARGIN_LEFT_DLU 3
+#define TOOLTIP_MARGIN_TOP_DLU 1
+#define TOOLTIP_MARGIN_RIGHT_DLU 3
+#define TOOLTIP_MARGIN_BOTTOM_DLU 1
+
+#define TOOLTIP_DELAY_INITIAL 1000
+#define TOOLTIP_DELAY_RESHOW 400
+
+static ATOM LISTWIDGETTOOLTIP_PROP = 0;
+
+static LRESULT WINAPI
+ListWidgetTooltip_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+typedef struct ListWidgetTooltip
+{
+ HWND window;
+ HWND owner;
+ BOOL active;
+ POINT position;
+ wchar_t *buffer;
+ ListWidgetItem *item;
+ ListWidgetItemPart part;
+ RECT partRect;
+ BOOL blockLocationChange;
+ WNDPROC originalProc;
+ DWORD showTime;
+} ListWidgetTooltip;
+
+
+static HWND
+ListWidget_TooltipCreateWindow(HWND owner, WNDPROC *originalProc, HANDLE windowProperty)
+{
+ HWND hwnd;
+ TOOLINFO ti;
+
+ hwnd = CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_NOPARENTNOTIFY | WS_EX_TRANSPARENT | WS_EX_LAYERED,
+ TOOLTIPS_CLASS, NULL, WS_CLIPSIBLINGS | WS_POPUP | TTS_NOANIMATE | TTS_ALWAYSTIP,
+ CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+ owner, NULL, NULL, NULL);
+
+ if ( hwnd == NULL )
+ return NULL;
+
+ MLSkinWindow2(Plugin_GetLibraryWindow(), hwnd, SKINNEDWND_TYPE_TOOLTIP,
+ SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
+
+ if (NULL != originalProc)
+ {
+ if (0 == LISTWIDGETTOOLTIP_PROP)
+ LISTWIDGETTOOLTIP_PROP = GlobalAddAtom(TEXT("ListWidgetTooltipProp"));
+
+ if (0 != LISTWIDGETTOOLTIP_PROP)
+ {
+ *originalProc = (WNDPROC)(LONG_PTR)SetWindowLongPtr(hwnd, GWLP_WNDPROC,
+ (LONGX86)(LONG_PTR)ListWidgetTooltip_WindowProc);
+ if (NULL != *originalProc &&
+ FALSE == SetProp(hwnd, MAKEINTATOM(LISTWIDGETTOOLTIP_PROP), windowProperty))
+ {
+ SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONGX86)(LONG_PTR)*originalProc);
+ *originalProc = NULL;
+ }
+ }
+ else
+ *originalProc = NULL;
+ }
+
+ SendMessage(hwnd, CCM_SETVERSION, 6, 0L);
+ SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER);
+
+ ZeroMemory(&ti, sizeof(ti));
+ ti.cbSize = sizeof(ti);
+ ti.hwnd = owner;
+ ti.lpszText = LPSTR_TEXTCALLBACK;
+ ti.uFlags = 0/*TTF_TRACK | TTF_ABSOLUTE*/;
+
+ SendMessage(hwnd, TTM_ADDTOOL, 0, (LPARAM)&ti);
+
+ return hwnd;
+}
+
+ListWidgetTooltip*
+ListWidget_TooltipCreate(HWND hwnd)
+{
+ ListWidgetTooltip *tooltip;
+
+ tooltip = (ListWidgetTooltip*)malloc(sizeof(ListWidgetTooltip));
+ if (NULL == tooltip)
+ return FALSE;
+
+ ZeroMemory(tooltip, sizeof(ListWidgetTooltip));
+
+ tooltip->window = ListWidget_TooltipCreateWindow(hwnd, &tooltip->originalProc, tooltip);
+ if (NULL == tooltip->window)
+ {
+ ListWidget_TooltipDestroy(tooltip);
+ return NULL;
+ }
+
+ tooltip->owner = hwnd;
+ ListWidget_TooltipFontChanged(tooltip);
+
+ return tooltip;
+}
+
+void
+ListWidget_TooltipDestroy(ListWidgetTooltip *tooltip)
+{
+ if (NULL != tooltip)
+ {
+ if (NULL != tooltip->window)
+ DestroyWindow(tooltip->window);
+
+ String_Free(tooltip->buffer);
+
+ free(tooltip);
+ }
+}
+
+void
+ListWidget_TooltipFontChanged(ListWidgetTooltip *tooltip)
+{
+ WidgetStyle *style;
+ RECT marginsRect;
+ HDC hdc;
+ long maxWidth;
+
+ if (NULL == tooltip)
+ return;
+
+ if (NULL == tooltip->window)
+ {
+ // attempt to recover...
+ tooltip->window = ListWidget_TooltipCreateWindow(tooltip->owner, &tooltip->originalProc, tooltip);
+ if (NULL == tooltip->window)
+ return;
+
+ tooltip->active = FALSE;
+ tooltip->part = ListWidgetItemPart_None;
+ SetRectEmpty(&tooltip->partRect);
+ }
+
+
+ style = WIDGET_GET_STYLE(tooltip->owner);
+ if (NULL == style)
+ return;
+
+ hdc = GetDCEx(tooltip->window, NULL, DCX_CACHE | DCX_NORESETATTRS);
+ if (NULL != hdc)
+ {
+ HFONT font = (HFONT)SendMessage(tooltip->window, WM_GETFONT, 0, 0L);
+ HFONT prevFont = SelectFont(hdc, font);
+
+ maxWidth = Graphics_GetAveStrWidth(hdc, 36);
+
+ SelectFont(hdc, prevFont);
+ ReleaseDC(tooltip->window, hdc);
+ }
+ else
+ maxWidth = 100;
+
+
+ SendMessage(tooltip->window, TTM_SETMAXTIPWIDTH, 0, (LPARAM)maxWidth);
+
+ WIDGETSTYLE_DLU_TO_HORZ_PX_MIN(marginsRect.left, style, TOOLTIP_MARGIN_LEFT_DLU, 3);
+ WIDGETSTYLE_DLU_TO_VERT_PX_MIN(marginsRect.top, style, TOOLTIP_MARGIN_TOP_DLU, 2);
+ WIDGETSTYLE_DLU_TO_HORZ_PX_MIN(marginsRect.right, style, TOOLTIP_MARGIN_RIGHT_DLU, 3);
+ WIDGETSTYLE_DLU_TO_VERT_PX_MIN(marginsRect.bottom, style, TOOLTIP_MARGIN_BOTTOM_DLU, 2);
+
+ SendMessage(tooltip->window, TTM_SETMARGIN, 0, (LPARAM)&marginsRect);
+}
+
+void
+ListWidget_TooltipRelayMouseMessage(ListWidgetTooltip *tooltip, unsigned int message, unsigned int vKeys, const POINT *cursor)
+{
+ if (NULL != tooltip &&
+ NULL != tooltip->window &&
+ FALSE != tooltip->active)
+ {
+ MSG msg;
+
+ msg.hwnd = tooltip->owner;
+ msg.message = message;
+ msg.wParam = (WPARAM)vKeys;
+ msg.lParam = MAKELPARAM(cursor->x, cursor->y);
+
+ SendMessage(tooltip->window, TTM_RELAYEVENT, 0, (LPARAM)&msg);
+ }
+}
+
+void
+ListWidget_TooltipHide(ListWidgetTooltip *tooltip)
+{
+ if (NULL == tooltip ||
+ NULL == tooltip->window ||
+ FALSE == tooltip->active)
+ {
+ return;
+ }
+
+ tooltip->active = FALSE;
+ tooltip->part = ListWidgetItemPart_None;
+ SetRectEmpty(&tooltip->partRect);
+
+ SendMessage(tooltip->window, TTM_ACTIVATE, FALSE, 0);
+}
+
+BOOL
+ListWidget_TooltipActivate(ListWidgetTooltip *tooltip, const RECT *rect)
+{
+ TOOLINFO ti;
+ POINT origin;
+
+ if (NULL == tooltip ||
+ NULL == tooltip->window)
+ {
+ return FALSE;
+ }
+
+ ZeroMemory(&ti, sizeof(ti));
+ ti.cbSize = sizeof(ti);
+ ti.hwnd = tooltip->owner;
+ ti.uId = 0;
+
+
+ if (FALSE == SendMessage(tooltip->window, TTM_GETTOOLINFO, 0, (LPARAM)&ti))
+ return FALSE;
+
+ if (FALSE != tooltip->active)
+ {
+ tooltip->active = FALSE;
+ SendMessage(tooltip->window, TTM_ACTIVATE, FALSE, 0);
+ }
+ else
+ SendMessage(tooltip->window, TTM_SETDELAYTIME, TTDT_INITIAL, MAKELPARAM(TOOLTIP_DELAY_INITIAL, 0));
+
+
+ CopyRect(&ti.rect, rect);
+ if (FALSE != ListWidget_GetViewOrigin(tooltip->owner, &origin))
+ OffsetRect(&ti.rect, origin.x, origin.y);
+
+ ti.lParam = NULL;
+ ti.lpszText = LPSTR_TEXTCALLBACK;
+
+ SendMessage(tooltip->window, TTM_SETTOOLINFO, 0, (LPARAM)&ti);
+
+
+ if (FALSE == tooltip->active)
+ {
+ KillTimer(tooltip->window, 4);
+ SendMessage(tooltip->window, TTM_ACTIVATE, TRUE, 0);
+ tooltip->active = TRUE;
+ }
+
+ return TRUE;
+}
+
+
+BOOL
+ListWidget_TooltipUpdate(ListWidgetTooltip *tooltip, ListWidgetItem *item,
+ ListWidgetItemPart part, const RECT *partRect)
+{
+ BOOL tooltipValid;
+
+ if (NULL == tooltip)
+ return FALSE;
+
+ if(FALSE == tooltip->active && NULL == item)
+ return FALSE;
+
+ if (0 != (ListWidgetItemPart_Activity & part))
+ {
+ part &= ~ListWidgetItemPart_Activity;
+ part |= ListWidgetItemPart_Frame;
+ }
+
+ tooltipValid = (NULL != item && ListWidgetItemPart_None != part);
+ if (tooltip->item == item &&
+ tooltip->part == part &&
+ (FALSE != (FALSE != tooltipValid) ?
+ EqualRect(&tooltip->partRect, partRect) :
+ IsRectEmpty(&tooltip->partRect)))
+ {
+
+ return FALSE;
+ }
+
+ tooltip->item = item;
+ tooltip->part = part;
+
+ if (FALSE != tooltipValid)
+ CopyRect(&tooltip->partRect, partRect);
+ else
+ SetRectEmpty(&tooltip->partRect);
+
+ if (FALSE == tooltipValid)
+ {
+ ListWidget_TooltipHide(tooltip);
+ return FALSE;
+ }
+
+ return ListWidget_TooltipActivate(tooltip, &tooltip->partRect);
+}
+
+static BOOL
+ListWidget_TooltipFormatTip(ListWidget *self, HWND hwnd,
+ const RECT *rect, wchar_t *buffer, size_t bufferMax)
+{
+ WidgetStyle *style;
+ ListWidgetItem *item;
+ ListWidgetItemPart part;
+ ListWidgetItemMetric metrics;
+ RECT partRect;
+ POINT pt, origin;
+
+ if (NULL == self || NULL == rect)
+ return FALSE;
+
+ style = WIDGET_GET_STYLE(hwnd);
+ if (NULL == style)
+ return FALSE;
+
+ if (FALSE == ListWidget_GetItemMetrics(style, &metrics))
+ return FALSE;
+
+ if (FALSE == ListWidget_GetViewOrigin(hwnd, &origin))
+ {
+ origin.x = 0;
+ origin.y = 0;
+ }
+
+ pt.x = (rect->left + (rect->right - rect->left)/2) - origin.x;
+ pt.y = (rect->top + (rect->bottom - rect->top)/2) - origin.y;
+
+
+ item = ListWidget_GetItemFromPoint(self, pt);
+ if (NULL == item)
+ return FALSE;
+
+
+ part = ListWidgetItemPart_Frame |
+ ListWidgetItemPart_Command |
+ ListWidgetItemPart_Spacebar |
+ ListWidgetItemPart_Title;
+
+ part = ListWidget_GetItemPartFromPoint(self, item, &metrics, pt, part, &partRect);
+ switch(part)
+ {
+ case ListWidgetItemPart_Command:
+ CopyRect(&partRect, rect);
+ OffsetRect(&partRect, -origin.x - item->rect.left, -origin.y - item->rect.top);
+ return ListWidget_FormatItemCommandTip(self, item, &partRect, buffer, bufferMax);
+
+ case ListWidgetItemPart_Activity:
+ case ListWidgetItemPart_Frame:
+ return ListWidget_FormatItemTip(self, item, buffer, bufferMax);
+
+ case ListWidgetItemPart_Spacebar:
+ return ListWidget_FormatItemSpaceTip(self, item, buffer, bufferMax);
+
+ case ListWidgetItemPart_Title:
+ return ListWidget_FormatItemTitleTip(self, item, buffer, bufferMax);
+ }
+
+ return FALSE;
+}
+
+static void
+ListWidget_TooltipGetDispInfo(ListWidget *self, ListWidgetTooltip *tooltip, NMTTDISPINFO *dispInfo)
+{
+ TOOLINFO ti;
+
+ if (NULL == dispInfo)
+ return;
+
+ ZeroMemory(&ti, sizeof(ti));
+ ti.cbSize = sizeof(ti);
+ ti.hwnd = tooltip->owner;
+ ti.uId = dispInfo->hdr.idFrom;
+
+ String_Free(tooltip->buffer);
+ tooltip->buffer = NULL;
+
+ if (FALSE != SendMessage(dispInfo->hdr.hwndFrom, TTM_GETTOOLINFO, 0, (LPARAM)&ti))
+ {
+ wchar_t buffer[4096] = {0};
+ if (FALSE != ListWidget_TooltipFormatTip(self, tooltip->owner, &ti.rect, buffer, ARRAYSIZE(buffer)))
+ tooltip->buffer = String_Duplicate(buffer);
+ }
+
+ dispInfo->lpszText = tooltip->buffer;
+ dispInfo->szText[0] = L'\0';
+ dispInfo->hinst = NULL;
+ dispInfo->uFlags = TTF_DI_SETITEM;
+}
+
+BOOL
+ListWidget_TooltipProcessNotification(ListWidget *self, ListWidgetTooltip *tooltip, NMHDR *pnmh, LRESULT *result)
+{
+ if (NULL == tooltip ||
+ NULL == pnmh ||
+ pnmh->hwndFrom != tooltip->window)
+ {
+ return FALSE;
+ }
+
+ switch(pnmh->code)
+ {
+ case TTN_GETDISPINFO:
+ ListWidget_TooltipGetDispInfo(self, tooltip, (NMTTDISPINFO*)pnmh);
+ break;
+
+ case TTN_SHOW:
+ if (0 == (WS_VISIBLE & GetWindowStyle(tooltip->window)))
+ tooltip->showTime = GetTickCount();
+
+ if (FALSE != tooltip->active)
+ {
+ SendMessage(tooltip->window, TTM_SETDELAYTIME, TTDT_INITIAL, MAKELPARAM(TOOLTIP_DELAY_RESHOW, 0));
+ }
+ break;
+ }
+
+ return TRUE;
+}
+
+ListWidgetItem *
+ListWidget_TooltipGetCurrent(ListWidgetTooltip *tooltip, ListWidgetItemPart *part, RECT *partRect)
+{
+ if (NULL == tooltip ||
+ FALSE == tooltip->active ||
+ NULL == tooltip->item)
+ {
+ return NULL;
+ }
+
+ if (NULL != part)
+ *part = tooltip->part;
+
+ if (NULL != partRect)
+ CopyRect(partRect, &tooltip->partRect);
+
+ return tooltip->item;
+}
+
+BOOL
+ListWidget_TooltipGetChanged(ListWidgetTooltip *tooltip, ListWidgetItem *item,
+ ListWidgetItemPart part, const RECT *partRect)
+{
+ if (NULL == tooltip)
+ return FALSE;
+
+ if (tooltip->item != item)
+ return TRUE;
+
+ if (NULL == item)
+ return FALSE;
+
+ if (tooltip->part != part)
+ return TRUE;
+
+ if (FALSE == EqualRect(&tooltip->partRect, partRect))
+ return TRUE;
+
+ return FALSE;
+}
+
+BOOL
+ListWidget_TooltipUpdateText(ListWidget *self, ListWidgetTooltip *tooltip, ListWidgetItem *item, TooltipUpdateReason reason)
+{
+ TOOLINFO ti;
+ ListWidgetItemPart mask;
+ unsigned int windowStyle;
+ unsigned long showTimeout, currentTime;
+
+ if (NULL == self ||
+ NULL == tooltip ||
+ NULL == tooltip->active ||
+ NULL == tooltip->window)
+ {
+ return FALSE;
+ }
+
+ if (NULL != item && tooltip->item != item)
+ return FALSE;
+
+ windowStyle = GetWindowStyle(tooltip->window);
+ if (0 == (WS_VISIBLE & windowStyle))
+ return FALSE;
+
+ showTimeout = (unsigned long)SendMessage(tooltip->window, TTM_GETDELAYTIME, (WPARAM)TTDT_AUTOPOP, 0L);
+ currentTime = GetTickCount();
+ if (currentTime < tooltip->showTime)
+ return FALSE;
+
+ currentTime -= tooltip->showTime;
+ if (showTimeout > (currentTime + 10))
+ showTimeout -= currentTime;
+ else
+ return FALSE;
+
+ KillTimer(tooltip->window, 4);
+
+ switch(reason)
+ {
+ case Tooltip_DeviceTitleChanged:
+ case Tooltip_DeviceModelChanged:
+ case Tooltip_DeviceStatusChanged:
+ mask = ListWidgetItemPart_Frame |
+ ListWidgetItemPart_Activity |
+ ListWidgetItemPart_Title;
+
+ if (0 == (mask & tooltip->part))
+ return FALSE;
+
+ break;
+
+ case Tooltip_DeviceSpaceChanged:
+ mask = ListWidgetItemPart_Frame |
+ ListWidgetItemPart_Activity |
+ ListWidgetItemPart_Spacebar;
+
+ if (0 == (mask & tooltip->part))
+ return FALSE;
+
+ break;
+
+ /*case Tooltip_DeviceActivityChanged:
+ mask = ListWidgetItemPart_Frame |
+ ListWidgetItemPart_Activity;
+
+ if (0 == (mask & tooltip->part))
+ return FALSE;
+
+ break;*/
+ }
+
+
+ ZeroMemory(&ti, sizeof(ti));
+ ti.cbSize = sizeof(ti);
+ ti.hwnd = tooltip->owner;
+ ti.uId = 0;
+ ti.lpszText = LPSTR_TEXTCALLBACK;
+
+ tooltip->blockLocationChange = TRUE;
+ SendMessage(tooltip->window, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti);
+ tooltip->blockLocationChange = FALSE;
+
+ SetTimer(tooltip->window, 4, showTimeout, 0);
+ return TRUE;
+}
+
+static LRESULT WINAPI
+ListWidgetTooltip_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ ListWidgetTooltip *tooltip;
+
+ tooltip = (ListWidgetTooltip*)GetProp(hwnd, MAKEINTATOM(LISTWIDGETTOOLTIP_PROP));
+ if (NULL == tooltip ||
+ NULL == tooltip->originalProc)
+ {
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+ }
+
+ switch(uMsg)
+ {
+ case WM_DESTROY:
+ RemoveProp(hwnd, MAKEINTATOM(LISTWIDGETTOOLTIP_PROP));
+ SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONGX86)(LONG_PTR)tooltip->originalProc);
+ CallWindowProc(tooltip->originalProc, hwnd, uMsg, wParam, lParam);
+ tooltip->originalProc = NULL;
+ tooltip->window = NULL;
+ break;
+ case WM_WINDOWPOSCHANGING:
+ if (FALSE != tooltip->blockLocationChange)
+ {
+ WINDOWPOS *pwp;
+ pwp = (WINDOWPOS*)lParam;
+ if (NULL != pwp)
+ {
+ pwp->flags |= SWP_NOMOVE;
+ }
+ }
+ break;
+ }
+
+ return CallWindowProc(tooltip->originalProc, hwnd, uMsg, wParam, lParam);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/local_menu.cpp b/Src/Plugins/Library/ml_devices/local_menu.cpp
new file mode 100644
index 00000000..446552f1
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/local_menu.cpp
@@ -0,0 +1,183 @@
+#include "main.h"
+#include "./local_menu.h"
+
+unsigned int
+Menu_InsertDeviceItems(HMENU menu, int position, unsigned int baseId,
+ ifc_device *device, DeviceCommandContext context)
+{
+ unsigned int count, separator;
+ MENUITEMINFO itemInfo = {0};
+ wchar_t itemName[512] = {0};
+
+ ifc_devicecommand *commandInfo;
+ ifc_devicesupportedcommandenum *enumerator;
+ ifc_devicesupportedcommand *command;
+ DeviceCommandFlags commandFlags;
+
+ if (NULL == device || NULL == menu)
+ return 0;
+
+ if (FAILED(device->EnumerateCommands(&enumerator, context)))
+ return 0;
+
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.fMask = MIIM_DATA | MIIM_ID | MIIM_FTYPE | MIIM_STATE | MIIM_STRING;
+
+ count = 0;
+ separator = 0;
+
+ while(S_OK == enumerator->Next(&command, 1, NULL))
+ {
+ if(SUCCEEDED(command->GetFlags(&commandFlags)))
+ {
+ if (0 != (DeviceCommandFlag_Group & commandFlags) &&
+ separator != count)
+ {
+ itemInfo.fType = MFT_SEPARATOR;
+ itemInfo.fState = MFS_ENABLED;
+ itemInfo.wID = 0xFFFE;
+ itemInfo.dwItemData = NULL;
+ if (0 != InsertMenuItem(menu, position + count, TRUE, &itemInfo))
+ {
+ count++;
+ separator = count;
+ }
+ }
+ if (0 == (DeviceCommandFlag_Hidden & commandFlags))
+ {
+ if (S_OK == WASABI_API_DEVICES->CommandFind(command->GetName(), &commandInfo))
+ {
+ if (SUCCEEDED(commandInfo->GetDisplayName(itemName, ARRAYSIZE(itemName))))
+ {
+ itemInfo.dwItemData = (ULONG_PTR)AnsiString_Duplicate(command->GetName());
+ if (NULL != itemInfo.dwItemData)
+ {
+ itemInfo.fType = MFT_STRING;
+ itemInfo.dwTypeData = itemName;
+ itemInfo.fState = 0;
+ itemInfo.wID = baseId + count;
+
+ if (0 == (DeviceCommandFlag_Disabled & commandFlags))
+ itemInfo.fState |= MFS_ENABLED;
+ else
+ itemInfo.fState |= (MFS_DISABLED | MFS_GRAYED);
+
+ if (0 != (DeviceCommandFlag_Primary & commandFlags))
+ itemInfo.fState |= MFS_DEFAULT;
+
+ if (0 != InsertMenuItem(menu, position + count, TRUE, &itemInfo))
+ count++;
+ else
+ AnsiString_Free((char*)itemInfo.dwItemData);
+ }
+
+ }
+ commandInfo->Release();
+ }
+ }
+ }
+ command->Release();
+ }
+
+
+ enumerator->Release();
+
+ return count;
+}
+
+unsigned int
+Menu_FreeItemData(HMENU menu, unsigned int start, int count)
+{
+ unsigned int processed;
+ MENUITEMINFO itemInfo;
+
+ if (NULL == menu)
+ return 0;
+
+ if (count < 0 )
+ count = GetMenuItemCount(menu);
+
+ if (start > (unsigned int)count)
+ return 0;
+
+ count -= start;
+ processed = 0;
+
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.fMask = MIIM_DATA;
+
+ while(count-- &&
+ FALSE != GetMenuItemInfo(menu, start + processed, TRUE, &itemInfo))
+ {
+ AnsiString_Free((char*)itemInfo.dwItemData);
+ processed++;
+ }
+
+ return processed;
+}
+
+ULONG_PTR
+Menu_GetItemData(HMENU menu, unsigned int item, BOOL byPosition)
+{
+ MENUITEMINFO itemInfo;
+
+ if (NULL == menu)
+ return 0;
+
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.fMask = MIIM_DATA;
+
+ if (FALSE != GetMenuItemInfo(menu, item, byPosition, &itemInfo))
+ return itemInfo.dwItemData;
+
+ return 0;
+}
+
+unsigned int
+Menu_FindItemByData(HMENU menu, Menu_FindItemByDataCb callback, void *user)
+{
+ int index, count;
+ MENUITEMINFO itemInfo;
+
+ if (NULL == menu || NULL == callback)
+ return -1;
+
+ count = GetMenuItemCount(menu);
+ if (0 == count)
+ return -1;
+
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.fMask = MIIM_DATA | MIIM_ID;
+
+ for (index = 0; index < count; index++)
+ {
+ if (FALSE != GetMenuItemInfo(menu, index, TRUE, &itemInfo) &&
+ FALSE != callback(itemInfo.dwItemData, user))
+ {
+ return itemInfo.wID;
+ }
+ }
+
+ return -1;
+
+}
+
+static BOOL
+Menu_FindItemByAnsiStringDataCb(ULONG_PTR param, void *user)
+{
+ const char *string1, *string2;
+
+ string1 = (const char*)param;
+ string2 = (const char*)user;
+
+ if (NULL == string1 || NULL == string2)
+ return (string1 == string2);
+
+ return (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, 0, string1, -1, string2, -1));
+}
+
+unsigned int
+Menu_FindItemByAnsiStringData(HMENU menu, const char *string)
+{
+ return Menu_FindItemByData(menu, Menu_FindItemByAnsiStringDataCb, (void*)string);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/local_menu.h b/Src/Plugins/Library/ml_devices/local_menu.h
new file mode 100644
index 00000000..1a94c209
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/local_menu.h
@@ -0,0 +1,48 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_MENU_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_MENU_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+int
+Menu_TrackSkinnedPopup(HMENU menu,
+ unsigned int flags,
+ int x,
+ int y,
+ HWND hwnd,
+ LPTPMPARAMS lptpm);
+
+unsigned int
+Menu_InsertDeviceItems(HMENU menu,
+ int position,
+ unsigned int baseId,
+ ifc_device *device,
+ DeviceCommandContext context);
+
+unsigned int
+Menu_FreeItemData(HMENU menu,
+ unsigned int start,
+ int count);
+
+ULONG_PTR
+Menu_GetItemData(HMENU menu,
+ unsigned int item,
+ BOOL byPosition);
+
+
+typedef BOOL (*Menu_FindItemByDataCb)(ULONG_PTR param, void *user);
+
+unsigned int
+Menu_FindItemByData(HMENU menu,
+ Menu_FindItemByDataCb callback,
+ void *user);
+
+unsigned int
+Menu_FindItemByAnsiStringData(HMENU menu,
+ const char *string);
+
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_MENU_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/main.cpp b/Src/Plugins/Library/ml_devices/main.cpp
new file mode 100644
index 00000000..da4d49da
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/main.cpp
@@ -0,0 +1,5 @@
+#include <initguid.h>
+#include "main.h"
+#define _ML_HEADER_IMPMLEMENT
+#include "../../General/gen_ml/ml_ipc_0313.h"
+#undef _ML_HEADER_IMPMLEMENT
diff --git a/Src/Plugins/Library/ml_devices/main.h b/Src/Plugins/Library/ml_devices/main.h
new file mode 100644
index 00000000..5fb70520
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/main.h
@@ -0,0 +1,50 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_MAIN_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_MAIN_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+// #define _CRTDBG_MAP_ALLOC
+
+#include <wtypes.h>
+
+#include "./common.h"
+#include "./strings.h"
+#include "./plugin.h"
+#include "./local_menu.h"
+#include "./graphics.h"
+#include "./image.h"
+#include "./imageCache.h"
+#include "./fillRegion.h"
+#include "./backBuffer.h"
+#include "./wasabi.h"
+#include "./config.h"
+#include "./navigation.h"
+#include "./navigationIcons.h"
+#include "./managerView.h"
+#include "./statusBar.h"
+#include "./widgetHost.h"
+#include "./widgetStyle.h"
+#include "./widget.h"
+#include "./infoWidget.h"
+#include "./welcomeWidget.h"
+#include "./listWidget.h"
+#include "./deviceManagerHandler.h"
+#include "./deviceHandler.h"
+#include "./eventRelay.h"
+#include "./deviceCommands.h"
+#include "./resource.h"
+#include "./embeddedEditor.h"
+
+#include "../../General/gen_ml/ml.h"
+#include "../../General/gen_ml/menu.h"
+#include "../../General/gen_ml/ml_ipc_0313.h"
+#include "../winamp/wa_ipc.h"
+#include "../winamp/wa_dlg.h"
+#include "../nu/trace.h"
+
+#include <shlwapi.h>
+#include <math.h>
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_MAIN_HEADER
diff --git a/Src/Plugins/Library/ml_devices/managerView.cpp b/Src/Plugins/Library/ml_devices/managerView.cpp
new file mode 100644
index 00000000..bba9679b
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/managerView.cpp
@@ -0,0 +1,994 @@
+#include "main.h"
+#include "./managerView.h"
+
+#define MANAGERVIEW_PROP L"NullsoftDevicesManagerViewProp"
+
+#define VIEW_OFFSET_LEFT_PX 0
+#define VIEW_OFFSET_TOP_PX 0
+#define VIEW_OFFSET_RIGHT_PX 2
+#define VIEW_OFFSET_BOTTOM_PX -1
+
+#define DISCOVER_BUTTON_MIN_HEIGHT_PX 18
+#define DISCOVER_BUTTON_MIN_WIDTH_PX 72
+#define DISCOVER_BUTTON_EXTRA_SPACE_DLU 8
+#define DISCOVER_BUTTON_SPACING_RIGHT_DLU 6
+
+#define ZOOM_SLIDER_SPACING_LEFT_DLU 6
+
+#define BOTTOM_BAR_OFFSET_TOP_DLU 2
+
+
+#define MANAGERVIEW_WIDGET_ID 10000
+#define MANAGERVIEW_STATUSBAR_ID 10001
+
+static ATOM MANAGERVIEW_ATOM = 0;
+
+
+typedef
+enum ManagerViewState
+{
+ MANAGERVIEW_STATE_FROZEN_UI = (1 << 0),
+} WelcomeViewState;
+DEFINE_ENUM_FLAG_OPERATORS(ManagerViewState);
+
+#define MANAGERVIEW_IS_FROZEN(_view) (0 != (MANAGERVIEW_STATE_FROZEN_UI & (_view)->state))
+#define MANAGERVIEW_FREEZE(_view) (((_view)->state) |= MANAGERVIEW_STATE_FROZEN_UI)
+#define MANAGERVIEW_THAW(_view) (((_view)->state) &= ~MANAGERVIEW_STATE_FROZEN_UI)
+
+#define MANAGERVIEW_DLU_TO_HORZ_PX(_view, _dlu) MulDiv((_dlu), (_view)->unitSize.cx, 4)
+#define MANAGERVIEW_DLU_TO_VERT_PX(_view, _dlu) MulDiv((_dlu), (_view)->unitSize.cy, 8)
+
+#define MANAGERVIEW_REGISTER_WIDGET(_widgetWindow) (SetWindowLongPtrW((_widgetWindow), GWLP_ID, MANAGERVIEW_WIDGET_ID))
+#define MANAGERVIEW_WIDGET(_viewWindow) (GetDlgItem((_viewWindow), MANAGERVIEW_WIDGET_ID))
+#define MANAGERVIEW_STATUS_BAR(_viewWindow) (GetDlgItem((_viewWindow), MANAGERVIEW_STATUSBAR_ID))
+#define MANAGERVIEW_DISCOVER_BUTTON(_viewWindow) (GetDlgItem((_viewWindow), IDC_BUTTON_DISCOVER))
+#define MANAGERVIEW_ZOOM_SLIDER(_viewWindow) (GetDlgItem((_viewWindow), IDC_SLIDER_ZOOM))
+
+typedef struct ManagerView
+{
+ ManagerViewState state;
+ WidgetStyle widgetStyle;
+ HFONT font;
+ HFONT systemFont;
+ COLORREF backColor;
+ COLORREF textColor;
+ COLORREF borderColor;
+ HBRUSH backBrush;
+ SIZE unitSize;
+ BOOL devicesPresent;
+ size_t deviceHandler;
+ HRGN updateRegion;
+ POINT updateOffset;
+ unsigned int discoveryStatus;
+} ManagerView;
+
+#define MANAGERVIEW(_hwnd) ((ManagerView*)GetPropW((_hwnd), MAKEINTATOM(MANAGERVIEW_ATOM)))
+#define MANAGERVIEW_RET_VOID(_view, _hwnd) { (_view) = MANAGERVIEW((_hwnd)); if (NULL == (_view)) return; }
+#define MANAGERVIEW_RET_VAL(_view, _hwnd, _error) { (_view) = MANAGERVIEW((_hwnd)); if (NULL == (_view)) return (_error); }
+
+static INT_PTR CALLBACK
+ManagerView_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+static void CALLBACK
+ManagerView_PluginUnloadCb()
+{
+ if (0 != MANAGERVIEW_ATOM)
+ {
+ GlobalDeleteAtom(MANAGERVIEW_ATOM);
+ MANAGERVIEW_ATOM = 0;
+ }
+}
+
+HWND ManagerView_CreateWindow(HWND parentWindow)
+{
+ if (0 == MANAGERVIEW_ATOM)
+ {
+ MANAGERVIEW_ATOM = GlobalAddAtom(MANAGERVIEW_PROP);
+ if (0 == MANAGERVIEW_ATOM)
+ return NULL;
+
+ Plugin_RegisterUnloadCallback(ManagerView_PluginUnloadCb);
+ }
+
+ HWND hwnd = WASABI_API_CREATEDIALOGPARAMW((INT_PTR)IDD_MANAGER_VIEW, parentWindow,
+ ManagerView_DialogProc, (LPARAM)0L);
+
+ return hwnd;
+}
+
+static void
+ManagerView_Layout(HWND hwnd, BOOL redraw)
+{
+ ManagerView *self = NULL;
+ RECT clientRect, elementRect;
+ LONG bottomBarHeight = 0, buttonWidth = 0, sliderWidth = 0;
+
+ MANAGERVIEW_RET_VOID(self, hwnd);
+
+ GetClientRect(hwnd, &clientRect);
+ clientRect.left += VIEW_OFFSET_LEFT_PX;
+ clientRect.top += VIEW_OFFSET_TOP_PX;
+ clientRect.right -= VIEW_OFFSET_RIGHT_PX;
+ clientRect.bottom -= VIEW_OFFSET_BOTTOM_PX;
+
+ HDWP hdwp = BeginDeferWindowPos(4);
+ if (NULL == hdwp)
+ return;
+
+ UINT swpFlags = SWP_NOACTIVATE | SWP_NOZORDER;
+ if (FALSE == redraw)
+ swpFlags |= SWP_NOREDRAW;
+
+ HWND buttonWindow = MANAGERVIEW_DISCOVER_BUTTON(hwnd);
+ if (NULL != buttonWindow)
+ {
+ if (FALSE != GetWindowRect(buttonWindow, &elementRect))
+ {
+ wchar_t buffer[128] = {0};
+ GetWindowText(buttonWindow, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(buttonWindow, buffer);
+
+ hdwp = DeferWindowPos(hdwp, buttonWindow, NULL, clientRect.left + 1,
+ clientRect.bottom - WASABI_API_APP->getScaleY(HIWORD(idealSize)) - WASABI_API_APP->getScaleY(1),
+ RECTWIDTH(elementRect), WASABI_API_APP->getScaleY(HIWORD(idealSize)), swpFlags);
+
+ bottomBarHeight = WASABI_API_APP->getScaleY(HIWORD(idealSize));
+ buttonWidth = RECTWIDTH(elementRect);
+ }
+ }
+
+ HWND sliderWindow = MANAGERVIEW_ZOOM_SLIDER(hwnd);
+ if (NULL != sliderWindow)
+ {
+ if (0 != (WS_VISIBLE & GetWindowStyle(sliderWindow)) &&
+ FALSE != GetWindowRect(sliderWindow, &elementRect))
+ {
+ hdwp = DeferWindowPos(hdwp, sliderWindow, NULL, clientRect.right - RECTWIDTH(elementRect),
+ clientRect.bottom - RECTHEIGHT(elementRect),
+ RECTWIDTH(elementRect), RECTHEIGHT(elementRect), swpFlags);
+ sliderWidth = RECTWIDTH(elementRect);
+ }
+ }
+
+ HWND statusBar = MANAGERVIEW_STATUS_BAR(hwnd);
+ if (NULL != statusBar)
+ {
+ long statusBarHeight = 0;
+ CopyRect(&elementRect, &clientRect);
+
+ if (0 != buttonWidth)
+ {
+ elementRect.left += buttonWidth;
+ elementRect.left += MANAGERVIEW_DLU_TO_HORZ_PX(self, DISCOVER_BUTTON_SPACING_RIGHT_DLU);
+ if (elementRect.left > clientRect.right)
+ elementRect.left = clientRect.right;
+ }
+
+ if (0 != sliderWidth)
+ {
+ elementRect.right -= sliderWidth;
+ elementRect.right -= MANAGERVIEW_DLU_TO_HORZ_PX(self, ZOOM_SLIDER_SPACING_LEFT_DLU);
+ if (elementRect.left > elementRect.right)
+ elementRect.right = elementRect.left;
+ }
+
+ statusBarHeight = STATUSBAR_GET_IDEAL_HEIGHT(statusBar);
+ if (statusBarHeight > bottomBarHeight)
+ statusBarHeight = 0;
+
+ elementRect.top = elementRect.bottom - statusBarHeight - WASABI_API_APP->getScaleY(3);
+ elementRect.bottom = elementRect.top + statusBarHeight;
+
+ hdwp = DeferWindowPos(hdwp, statusBar, NULL, elementRect.left, elementRect.top,
+ RECTWIDTH(elementRect), RECTHEIGHT(elementRect), swpFlags);
+ }
+
+ HWND widgetWindow = MANAGERVIEW_WIDGET(hwnd);
+ if (NULL != widgetWindow)
+ {
+ CopyRect(&elementRect, &clientRect);
+ if (0 != bottomBarHeight)
+ {
+ elementRect.bottom -= (bottomBarHeight + WASABI_API_APP->getScaleY(4));
+ }
+
+ Graphics_ClampRect(&elementRect, &clientRect);
+
+ if (NULL != hdwp)
+ {
+ hdwp = DeferWindowPos(hdwp, widgetWindow, NULL, elementRect.left, elementRect.top,
+ RECTWIDTH(elementRect), RECTHEIGHT(elementRect), swpFlags);
+ }
+ }
+
+ if (NULL != hdwp)
+ EndDeferWindowPos(hdwp);
+}
+
+static BOOL
+ManagerView_Paint(HWND hwnd, HDC hdc, const RECT *paintRect, BOOL erase)
+{
+ ManagerView *self;
+ MANAGERVIEW_RET_VAL(self, hwnd, FALSE);
+
+ if (FALSE != erase)
+ {
+ HRGN fillRegion;
+ fillRegion = CreateRectRgnIndirect(paintRect);
+ FillRgn(hdc, fillRegion, self->backBrush);
+ DeleteObject(fillRegion);
+ }
+
+ return TRUE;
+}
+
+static void
+ManagerView_UpdateSkin(HWND hwnd)
+{
+ ManagerView *self;
+ MANAGERVIEW_RET_VOID(self, hwnd);
+
+ BOOL styleChanged = FALSE;
+ COLORREF color = Graphics_GetSkinColor(WADLG_WNDBG);
+ if (color != self->backColor || NULL == self->backBrush)
+ {
+ self->backColor = color;
+ self->backBrush = CreateSolidBrush(self->backColor);
+ styleChanged = TRUE;
+ }
+
+ color = Graphics_GetSkinColor(WADLG_WNDFG);
+ if (self->textColor != color)
+ {
+ self->textColor = color;
+ styleChanged = TRUE;
+ }
+
+ color = Graphics_GetSkinColor(WADLG_HILITE);
+ if (self->borderColor != color)
+ {
+ self->borderColor = color;
+ styleChanged = TRUE;
+ }
+
+ if (FALSE != WidgetStyle_UpdateDefaultColors(&self->widgetStyle))
+ styleChanged = TRUE;
+
+ if (FALSE != styleChanged)
+ {
+ HWND controlWindow = MANAGERVIEW_WIDGET(hwnd);
+ if (NULL != controlWindow)
+ {
+ WIDGET_STYLE_COLOR_CHANGED(controlWindow);
+ InvalidateRect(controlWindow, NULL, TRUE);
+ }
+
+ controlWindow = MANAGERVIEW_STATUS_BAR(hwnd);
+ if (NULL != controlWindow)
+ {
+ STATUSBAR_SET_TEXT_COLOR(controlWindow, self->textColor, FALSE);
+ STATUSBAR_SET_BACK_COLOR(controlWindow, self->backColor, FALSE);
+ STATUSBAR_SET_BACK_BRUSH(controlWindow, self->backBrush, FALSE);
+ }
+ }
+}
+
+static BOOL
+ManagerView_GetIdealButtonSize(HWND buttonWindow, SIZE *buttonSize)
+{
+ if (NULL == buttonWindow || NULL == buttonSize)
+ return FALSE;
+
+ LRESULT skinSize = MLSkinnedButton_GetIdealSize(buttonWindow, NULL);
+ if (0 != skinSize)
+ {
+ buttonSize->cx = LOWORD(skinSize);
+ buttonSize->cy = HIWORD(skinSize);
+ return TRUE;
+ }
+
+ buttonSize->cx = 0;
+ buttonSize->cy = 0;
+ if (FALSE != SendMessageW(buttonWindow, BCM_GETIDEALSIZE, 0, (LPARAM)buttonSize))
+ return TRUE;
+
+ return FALSE;
+}
+
+static BOOL
+ManagerView_UpdateDiscoverButtonFont(HWND hwnd, HFONT font, SIZE *size)
+{
+ ManagerView *self;
+ SIZE buttonSize;
+
+ MANAGERVIEW_RET_VAL(self, hwnd, FALSE);
+
+ HWND buttonWindow = MANAGERVIEW_DISCOVER_BUTTON(hwnd);
+ if (NULL == buttonWindow)
+ return FALSE;
+
+ SendMessage(buttonWindow, WM_SETFONT, (WPARAM)font, MAKELPARAM(0,0));
+
+ if (FALSE == ManagerView_GetIdealButtonSize(buttonWindow, &buttonSize))
+ {
+ RECT buttonRect;
+ if (FALSE == GetWindowRect(buttonWindow, &buttonRect))
+ return FALSE;
+
+ buttonSize.cx = RECTWIDTH(buttonRect);
+ buttonSize.cy = RECTHEIGHT(buttonRect);
+ }
+
+ buttonSize.cx += MANAGERVIEW_DLU_TO_HORZ_PX(self, DISCOVER_BUTTON_EXTRA_SPACE_DLU);
+
+ if (buttonSize.cx < DISCOVER_BUTTON_MIN_WIDTH_PX)
+ buttonSize.cx = DISCOVER_BUTTON_MIN_WIDTH_PX;
+
+ if (buttonSize.cy < DISCOVER_BUTTON_MIN_HEIGHT_PX)
+ buttonSize.cy = DISCOVER_BUTTON_MIN_HEIGHT_PX;
+
+ BOOL result = SetWindowPos(buttonWindow, NULL, 0, 0, buttonSize.cx, buttonSize.cy,
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE | SWP_NOREDRAW);
+ if (FALSE == result)
+ return FALSE;
+
+ if (NULL != size)
+ *size = buttonSize;
+
+ return TRUE;
+}
+
+static void
+ManagerView_UpdateFont(HWND hwnd, BOOL redraw)
+{
+ HWND controlWindow = NULL;
+ ManagerView *self = NULL;
+ MANAGERVIEW_RET_VOID(self, hwnd);
+
+ if (FALSE == Graphics_GetWindowBaseUnits(hwnd, &self->unitSize.cx, &self->unitSize.cy))
+ {
+ self->unitSize.cx = 6;
+ self->unitSize.cy = 13;
+ }
+
+ if (FALSE != WidgetStyle_UpdateDefaultFonts(&self->widgetStyle, self->font, self->unitSize.cx, self->unitSize.cy))
+ {
+ controlWindow = MANAGERVIEW_WIDGET(hwnd);
+ if (NULL != controlWindow)
+ WIDGET_STYLE_FONT_CHANGED(controlWindow);
+ }
+
+ ManagerView_UpdateDiscoverButtonFont(hwnd, self->font, NULL);
+
+ controlWindow = MANAGERVIEW_STATUS_BAR(hwnd);
+ if (NULL != controlWindow)
+ SendMessage(controlWindow, WM_SETFONT, (WPARAM)self->font, 0L);
+
+ if (NULL != redraw)
+ {
+ SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE |SWP_NOMOVE |SWP_FRAMECHANGED | SWP_NOREDRAW);
+
+ RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_ERASE);
+ }
+}
+
+static void
+ManagerView_UpdateDiscoveryStatus(HWND hwnd, BOOL discoveryActive)
+{
+ ManagerView *self;
+ MANAGERVIEW_RET_VOID(self, hwnd);
+
+ HWND statusBar = MANAGERVIEW_STATUS_BAR(hwnd);
+ if (NULL == statusBar)
+ return;
+
+ if (FALSE != discoveryActive)
+ {
+ wchar_t buffer[512] = {0};
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_STATUS_DISCOVERY_ACTIVE, buffer, ARRAYSIZE(buffer));
+
+ if (STATUS_ERROR == self->discoveryStatus)
+ self->discoveryStatus = STATUSBAR_ADD_STATUS(statusBar, buffer);
+ else
+ {
+ STATUSBAR_SET_STATUS_TEXT(statusBar, self->discoveryStatus, buffer);
+ STATUSBAR_MOVE_STATUS(statusBar, self->discoveryStatus, STATUS_MOVE_TOP);
+ }
+ }
+ else
+ {
+ if (STATUS_ERROR != self->discoveryStatus)
+ {
+ STATUSBAR_REMOVE_STATUS(statusBar, self->discoveryStatus);
+ self->discoveryStatus = STATUS_ERROR;
+ }
+ }
+}
+
+static HWND
+ManagerView_CreateServiceErrorWidget(HWND hwnd)
+{
+ return InfoWidget_CreateWindow(WIDGET_TYPE_SERVICE_ERROR,
+ MAKEINTRESOURCE(IDS_INFOWIDGET_TITLE),
+ MAKEINTRESOURCE(IDS_DEVICE_SERVICE_NOT_FOUND),
+ NULL,
+ hwnd, 0, 0, 0, 0, TRUE, 0);
+}
+
+static HWND
+ManagerView_CreateViewErrorWidget(HWND hwnd)
+{
+ return InfoWidget_CreateWindow(WIDGET_TYPE_VIEW_ERROR,
+ MAKEINTRESOURCE(IDS_INFOWIDGET_TITLE),
+ MAKEINTRESOURCE(IDS_CREATE_MANAGER_VIEW_FAILED),
+ NULL,
+ hwnd, 0, 0, 0, 0, TRUE, 0);
+}
+
+static HWND
+ManagerView_UpdateWidget(HWND hwnd)
+{
+ ManagerView *self;
+ unsigned int widgetType, requiredType;
+ unsigned int windowStyle;
+
+ MANAGERVIEW_RET_VAL(self, hwnd, NULL);
+
+ HWND widgetWindow = MANAGERVIEW_WIDGET(hwnd);
+ if (NULL == widgetWindow)
+ widgetType = WIDGET_TYPE_UNKNOWN;
+ else
+ widgetType = WIDGET_GET_TYPE(widgetWindow);
+
+ if (FALSE != self->devicesPresent)
+ requiredType = WIDGET_TYPE_LIST;
+ else
+ {
+ requiredType = (NULL != WASABI_API_DEVICES) ?
+ WIDGET_TYPE_WELCOME :
+ WIDGET_TYPE_SERVICE_ERROR;
+ }
+
+ if (widgetType == requiredType)
+ return widgetWindow;
+
+ windowStyle = GetWindowStyle(hwnd);
+ if (0 != (WS_VISIBLE & windowStyle))
+ SetWindowStyle(hwnd, windowStyle & ~WS_VISIBLE);
+
+ if (NULL != widgetWindow)
+ {
+ SetWindowLongPtr(widgetWindow, GWLP_ID, 0);
+ WIDGET_FREEZE(widgetWindow);
+ DestroyWindow(widgetWindow);
+ }
+
+ switch(requiredType)
+ {
+ case WIDGET_TYPE_SERVICE_ERROR:
+ widgetWindow = ManagerView_CreateServiceErrorWidget(hwnd);
+ break;
+ case WIDGET_TYPE_WELCOME:
+ widgetWindow = WelcomeWidget_CreateWindow(hwnd, 0, 0, 0, 0, TRUE, 0);
+ break;
+ case WIDGET_TYPE_LIST:
+ widgetWindow = ListWidget_CreateWindow(hwnd, 0, 0, 0, 0, TRUE, 0);
+ break;
+ default:
+ widgetWindow = NULL;
+ break;
+ }
+
+ if (NULL == widgetWindow)
+ widgetWindow = ManagerView_CreateViewErrorWidget(hwnd);
+
+ if (NULL != widgetWindow)
+ {
+ if (FALSE != MANAGERVIEW_IS_FROZEN(self))
+ WIDGET_FREEZE(widgetWindow);
+
+ MANAGERVIEW_REGISTER_WIDGET(widgetWindow);
+
+ WIDGET_SET_STYLE(widgetWindow, &self->widgetStyle);
+ }
+
+ SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
+ SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOREDRAW | SWP_FRAMECHANGED);
+
+ if (NULL != widgetWindow)
+ {
+ ShowWindow(widgetWindow, SW_SHOWNA);
+
+ SetWindowPos(widgetWindow, HWND_TOP, 0, 0, 0, 0,
+ SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
+ }
+
+ if (0 != (WS_VISIBLE & windowStyle))
+ {
+ windowStyle = GetWindowStyle(hwnd);
+ if (0 == (WS_VISIBLE & windowStyle))
+ {
+ windowStyle |= WS_VISIBLE;
+ SetWindowStyle(hwnd, windowStyle);
+ }
+ }
+
+ if (0 != (WS_VISIBLE & windowStyle))
+ {
+ RECT rect;
+ GetClientRect(hwnd, &rect);
+
+ RedrawWindow(hwnd, &rect, NULL,
+ RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN);
+ }
+
+ return widgetWindow;
+}
+
+static void
+ManagerView_StartDiscovery(HWND hwnd, BOOL silent)
+{
+ Plugin_BeginDiscovery();
+}
+
+static BOOL
+ManagerView_CheckDevicesPresent()
+{
+ ifc_deviceobjectenum *enumerator = 0;
+ ifc_deviceobject *object = 0;
+ ifc_device *device = 0;
+
+ BOOL devicesPresent = FALSE;
+
+ if (NULL == WASABI_API_DEVICES ||
+ FAILED(WASABI_API_DEVICES->DeviceEnumerate(&enumerator)))
+ {
+ return FALSE;
+ }
+
+ while(S_OK == enumerator->Next(&object, 1, NULL))
+ {
+ if (SUCCEEDED(object->QueryInterface(IFC_Device, (void**)&device)))
+ {
+ if(FALSE == device->GetHidden() &&
+ // excludes 'cloud' devices from appearing
+ lstrcmpiA(device->GetConnection(), "cloud"))
+ devicesPresent = TRUE;
+
+ device->Release();
+ }
+ object->Release();
+
+ if (FALSE != devicesPresent)
+ break;
+ }
+ enumerator->Release();
+
+ return devicesPresent;
+}
+
+static void
+ManagerView_AddDevice(HWND hwnd, ifc_device *device)
+{
+ ManagerView *self;
+
+ MANAGERVIEW_RET_VOID(self, hwnd);
+
+ if (FALSE != self->devicesPresent)
+ return;
+
+ self->devicesPresent = ManagerView_CheckDevicesPresent();
+ if (FALSE != self->devicesPresent)
+ ManagerView_UpdateWidget(hwnd);
+}
+
+static void
+ManagerView_RemoveDevice(HWND hwnd, ifc_device *device)
+{
+ ManagerView *self;
+ MANAGERVIEW_RET_VOID(self, hwnd);
+
+ if (FALSE == self->devicesPresent)
+ return;
+
+ self->devicesPresent = ManagerView_CheckDevicesPresent();
+ if (FALSE == self->devicesPresent)
+ ManagerView_UpdateWidget(hwnd);
+}
+
+static void
+ManagerView_DeviceCb(ifc_device *device, DeviceEvent eventId, void *user)
+{
+ HWND hwnd = (HWND)user;
+
+ switch(eventId)
+ {
+ case Event_DeviceAdded:
+ ManagerView_AddDevice(hwnd, device);
+ break;
+
+ case Event_DeviceRemoved:
+ ManagerView_RemoveDevice(hwnd, device);
+ break;
+ }
+}
+
+static void
+ManagerView_DiscoveryCb(api_devicemanager *manager, DeviceDiscoveryEvent eventId, void *user)
+{
+ HWND hwnd = (HWND)user;
+
+ switch(eventId)
+ {
+ case Event_DiscoveryStarted:
+ ManagerView_UpdateDiscoveryStatus(hwnd, TRUE);
+ break;
+
+ case Event_DiscoveryFinished:
+ ManagerView_UpdateDiscoveryStatus(hwnd, FALSE);
+ break;
+ }
+}
+
+static BOOL
+ManagerView_RegisterDeviceHandler(HWND hwnd)
+{
+ ManagerView *self;
+ DeviceEventCallbacks callbacks;
+
+ MANAGERVIEW_RET_VAL(self, hwnd, FALSE);
+
+ if (0 != self->deviceHandler)
+ return FALSE;
+
+ HWND eventRelay = Plugin_GetEventRelayWindow();
+ if (NULL == eventRelay)
+ return FALSE;
+
+ ZeroMemory(&callbacks, sizeof(callbacks));
+ callbacks.deviceCb = ManagerView_DeviceCb;
+ callbacks.discoveryCb = ManagerView_DiscoveryCb;
+
+ self->deviceHandler = EVENTRELAY_REGISTER_HANDLER(eventRelay, &callbacks, hwnd);
+ return (0 != self->deviceHandler);
+}
+
+static void
+ManagerView_OnDisplayChanged(HWND hwnd, INT bpp, INT dpi_x, INT dpi_y)
+{
+ ManagerView *self = NULL;
+ MANAGERVIEW_RET_VOID(self, hwnd);
+
+ if (FALSE != MANAGERVIEW_IS_FROZEN(self))
+ return;
+
+ ManagerView_UpdateSkin(hwnd);
+
+ RECT rc;
+ GetClientRect(hwnd, &rc);
+ RedrawWindow(hwnd, &rc, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN | RDW_ERASENOW | RDW_UPDATENOW);
+
+ ManagerView_Layout(hwnd, TRUE);
+}
+
+static INT_PTR
+ManagerView_OnInitDialog(HWND hwnd, HWND focusWindow, LPARAM param)
+{
+ ManagerView *self = (ManagerView*)malloc(sizeof(ManagerView));
+ if (NULL != self)
+ {
+ ZeroMemory(self, sizeof(ManagerView));
+ if (FALSE == SetProp(hwnd, MAKEINTATOM(MANAGERVIEW_ATOM), self))
+ {
+ free(self);
+ self = NULL;
+ }
+ }
+
+ if (NULL == self)
+ {
+ DestroyWindow(hwnd);
+ return 0;
+ }
+
+ MANAGERVIEW_FREEZE(self);
+
+ ManagerView_RegisterDeviceHandler(hwnd);
+
+ self->devicesPresent = ManagerView_CheckDevicesPresent();
+
+ MLSkinWindow2(Plugin_GetLibraryWindow(), hwnd, SKINNEDWND_TYPE_DIALOG,
+ SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
+
+ HWND discoverButton = MANAGERVIEW_DISCOVER_BUTTON(hwnd);
+ if (NULL != discoverButton)
+ {
+ MLSkinWindow2(Plugin_GetLibraryWindow(), discoverButton, SKINNEDWND_TYPE_BUTTON,
+ SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
+
+ ShowWindow(discoverButton, SW_SHOW);
+ EnableWindow(discoverButton, TRUE);
+ }
+
+ HWND statusBar = StatusBar_CreateWindow(0, NULL, WS_VISIBLE, 0, 0, 0, 0, hwnd, MANAGERVIEW_STATUSBAR_ID);
+ if (NULL != statusBar)
+ {
+ MLSkinWindow2(Plugin_GetLibraryWindow(), statusBar, SKINNEDWND_TYPE_AUTO,
+ SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
+ }
+
+ self->discoveryStatus = STATUS_ERROR;
+
+ if (S_OK == WASABI_API_DEVICES->IsDiscoveryActive())
+ ManagerView_UpdateDiscoveryStatus(hwnd, TRUE);
+
+ ManagerView_UpdateFont(hwnd, FALSE);
+ ManagerView_UpdateSkin(hwnd);
+
+ HWND widgetWindow = ManagerView_UpdateWidget(hwnd);
+ MANAGERVIEW_THAW(self);
+
+ if (NULL != widgetWindow)
+ WIDGET_THAW(widgetWindow);
+
+ PostMessage(hwnd, WM_DISPLAYCHANGE, 0, 0);
+
+ return 0;
+}
+
+static void
+ManagerView_OnDestroy(HWND hwnd)
+{
+ ManagerView *self = MANAGERVIEW(hwnd);
+ RemoveProp(hwnd, MAKEINTATOM(MANAGERVIEW_ATOM));
+
+ if (NULL == self)
+ return;
+
+ MANAGERVIEW_FREEZE(self);
+
+ if (0 != self->deviceHandler)
+ {
+ HWND eventRelay = Plugin_GetEventRelayWindow();
+ if (NULL != eventRelay)
+ {
+ EVENTRELAY_UNREGISTER_HANDLER(eventRelay, self->deviceHandler);
+ }
+ }
+
+ if (NULL != self->systemFont)
+ DeleteObject(self->systemFont);
+
+ if (NULL != self->backBrush)
+ DeleteObject(self->backBrush);
+
+ WidgetStyle_Free(&self->widgetStyle);
+ free(self);
+}
+
+static LRESULT
+ManagerView_OnColorDialog(HWND hwnd, HDC hdc)
+{
+ ManagerView *self;
+ self = MANAGERVIEW(hwnd);
+
+ if (NULL == self)
+ return DefWindowProcW(hwnd, WM_CTLCOLORDLG, (WPARAM)hdc, (LPARAM)hwnd);
+
+ if (NULL != hdc)
+ {
+ SetTextColor(hdc, self->textColor);
+ SetBkColor(hdc, self->backColor);
+ }
+
+ return (LRESULT)self->backBrush;
+}
+
+static void
+ManagerView_OnWindowPosChanged(HWND hwnd, WINDOWPOS *windowPos)
+{
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & windowPos->flags) ||
+ (SWP_FRAMECHANGED & windowPos->flags))
+ {
+ ManagerView *self;
+ MANAGERVIEW_RET_VOID(self, hwnd);
+
+ if (FALSE != MANAGERVIEW_IS_FROZEN(self))
+ return;
+
+ ManagerView_Layout(hwnd, !(SWP_NOREDRAW & windowPos->flags));
+ }
+}
+
+static void
+ManagerView_OnPaint(HWND hwnd)
+{
+ PAINTSTRUCT ps;
+
+ if (NULL != BeginPaint(hwnd, &ps))
+ {
+ ManagerView_Paint(hwnd, ps.hdc, &ps.rcPaint, ps.fErase);
+ EndPaint(hwnd, &ps);
+ }
+}
+
+static void
+ManagerView_OnPrintClient(HWND hwnd, HDC hdc, UINT options)
+{
+ RECT clientRect;
+ if (GetClientRect(hwnd, &clientRect))
+ {
+ ManagerView_Paint(hwnd, hdc, &clientRect, TRUE);
+ }
+}
+
+static void
+ManagerView_OnSetFont(HWND hwnd, HFONT font, BOOL redraw)
+{
+ ManagerView *self = NULL;
+ LOGFONTW prevFont = {0}, newFont = {0};
+
+ MANAGERVIEW_RET_VOID(self, hwnd);
+
+ if (NULL == self->font ||
+ sizeof(LOGFONTW) != GetObjectW(self->font, sizeof(prevFont), &prevFont))
+ {
+ ZeroMemory(&prevFont, sizeof(prevFont));
+ }
+
+ self->font = font;
+ if (NULL == self->font)
+ {
+ if (NULL == self->systemFont)
+ self->systemFont = Graphics_CreateSysFont();
+ }
+
+ if (NULL == self->font ||
+ sizeof(newFont) != GetObjectW(self->font, sizeof(newFont), &newFont))
+ {
+ ZeroMemory(&newFont, sizeof(newFont));
+ }
+
+ if (0 == memcmp(&prevFont, &newFont, sizeof(prevFont)) ||
+ FALSE != MANAGERVIEW_IS_FROZEN(self))
+ {
+ redraw = FALSE;
+ }
+
+ ManagerView_UpdateFont(hwnd, redraw);
+}
+
+static HFONT
+ManagerView_OnGetFont(HWND hwnd)
+{
+ ManagerView *self;
+ MANAGERVIEW_RET_VAL(self, hwnd, NULL);
+
+ return self->font;
+}
+
+static void
+ManagerView_OnCommand(HWND hwnd, INT commandId, INT eventId, HWND controlWindow)
+{
+ switch(commandId)
+ {
+ case IDC_BUTTON_DISCOVER:
+ switch(eventId)
+ {
+ case BN_CLICKED:
+ ManagerView_StartDiscovery(hwnd, FALSE);
+ break;
+ }
+ break;
+ }
+}
+
+static void
+ManagerView_OnZoomSliderPosChanging(HWND hwnd, NMTRBTHUMBPOSCHANGING *sliderInfo)
+{
+ HWND widgetWindow = MANAGERVIEW_WIDGET(hwnd);
+ if (NULL != widgetWindow)
+ WIDGET_ZOOM_SLIDER_POS_CHANGING(widgetWindow, sliderInfo);
+}
+
+static LRESULT
+ManagerView_OnNotify(HWND hwnd, NMHDR *pnmh)
+{
+ return 0;
+}
+
+static void
+ManagerView_OnHorzScroll(HWND hwnd, INT action, INT trackPosition, HWND senderWindow)
+{
+ HWND sliderWindow = MANAGERVIEW_ZOOM_SLIDER(hwnd);
+ if (NULL != sliderWindow && senderWindow == sliderWindow)
+ {
+ NMTRBTHUMBPOSCHANGING zoomInfo;
+ zoomInfo.hdr.code = TRBN_THUMBPOSCHANGING;
+ zoomInfo.hdr.hwndFrom = senderWindow;
+ zoomInfo.hdr.idFrom = IDC_SLIDER_ZOOM;
+ zoomInfo.nReason = action;
+ if (TB_THUMBPOSITION == action ||
+ TB_THUMBTRACK == action)
+ {
+ zoomInfo.dwPos = trackPosition;
+ }
+ else
+ zoomInfo.dwPos = (DWORD)SendMessage(sliderWindow, TBM_GETPOS, 0, 0L);
+
+ ManagerView_OnZoomSliderPosChanging(hwnd, &zoomInfo);
+ }
+}
+
+static BOOL
+ManagerView_OnHelp(HWND hwnd, HELPINFO *helpInfo)
+{
+ HWND widgetWindow = MANAGERVIEW_WIDGET(hwnd);
+ if (NULL != widgetWindow)
+ {
+ wchar_t buffer[4096] = {0};
+
+ if (FALSE != WIDGET_GET_HELP_URL(widgetWindow, buffer, ARRAYSIZE(buffer)) &&
+ MediaLibrary_ShowHelp(Plugin_GetLibraryWindow(), buffer))
+ {
+ return TRUE;
+ }
+ }
+
+ return Plugin_ShowHelp();
+}
+
+static void
+ManagerView_OnSetUpdateRegion(HWND hwnd, HRGN updateRegion, POINTS regionOffset)
+{
+ ManagerView *self;
+ MANAGERVIEW_RET_VOID(self, hwnd);
+
+ self->updateRegion = updateRegion;
+ self->updateOffset.x = regionOffset.x;
+ self->updateOffset.y = regionOffset.y;
+}
+
+static HWND
+ManagerView_OnGetZoomSlider(HWND hwnd)
+{
+ return MANAGERVIEW_ZOOM_SLIDER(hwnd);
+}
+
+static HWND
+ManagerView_OnGetStatusBar(HWND hwnd)
+{
+ return MANAGERVIEW_STATUS_BAR(hwnd);
+}
+
+static INT_PTR CALLBACK
+ManagerView_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return ManagerView_OnInitDialog(hwnd, (HWND)wParam, lParam);
+ case WM_DESTROY: ManagerView_OnDestroy(hwnd); return TRUE;
+ case WM_CTLCOLORDLG: return ManagerView_OnColorDialog(hwnd, (HDC)wParam);
+ case WM_PAINT: ManagerView_OnPaint(hwnd); return TRUE;
+ case WM_PRINTCLIENT: ManagerView_OnPrintClient(hwnd, (HDC)wParam, (UINT)lParam); return TRUE;
+ case WM_ERASEBKGND: DIALOG_RESULT(hwnd, 0);
+ case WM_WINDOWPOSCHANGED: ManagerView_OnWindowPosChanged(hwnd, (WINDOWPOS*)lParam); return TRUE;
+ case WM_DISPLAYCHANGE: ManagerView_OnDisplayChanged(hwnd, (INT)wParam, LOWORD(lParam), HIWORD(lParam)); return TRUE;
+ case WM_SETFONT: ManagerView_OnSetFont(hwnd, (HFONT)wParam, LOWORD(lParam)); return TRUE;
+ case WM_GETFONT: DIALOG_RESULT(hwnd, ManagerView_OnGetFont(hwnd));
+ case WM_COMMAND: ManagerView_OnCommand(hwnd, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); return TRUE;
+ case WM_NOTIFY: DIALOG_RESULT(hwnd, ManagerView_OnNotify(hwnd, (NMHDR*)lParam));
+ case WM_HSCROLL: ManagerView_OnHorzScroll(hwnd, LOWORD(wParam), (short)HIWORD(wParam), (HWND)lParam); return TRUE;
+ case WM_HELP: DIALOG_RESULT(hwnd, ManagerView_OnHelp(hwnd, (HELPINFO*)lParam));
+
+ // gen_ml flickerless drawing
+ case WM_USER + 0x200: DIALOG_RESULT(hwnd, 1);
+ case WM_USER + 0x201: ManagerView_OnSetUpdateRegion(hwnd, (HRGN)lParam, MAKEPOINTS(wParam)); return TRUE;
+
+ case MANAGERVIEW_WM_ZOOMSLIDER: DIALOG_RESULT(hwnd, ManagerView_OnGetZoomSlider(hwnd));
+ case MANAGERVIEW_WM_STATUSBAR: DIALOG_RESULT(hwnd, ManagerView_OnGetStatusBar(hwnd));
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/managerView.h b/Src/Plugins/Library/ml_devices/managerView.h
new file mode 100644
index 00000000..af32d790
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/managerView.h
@@ -0,0 +1,22 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_MANAGER_VIEW_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_MANAGER_VIEW_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+HWND ManagerView_CreateWindow(HWND parentWindow);
+
+#define MANAGERVIEW_WM_FIRST (WM_USER + 10)
+
+#define MANAGERVIEW_WM_ZOOMSLIDER (MANAGERVIEW_WM_FIRST + 0)
+#define MANAGERVIEW_GET_ZOOM_SLIDER(/*HWND*/ _hwnd)\
+ ((HWND)SendMessageW((_hwnd), MANAGERVIEW_WM_ZOOMSLIDER, 0, 0L))
+
+#define MANAGERVIEW_WM_STATUSBAR (MANAGERVIEW_WM_FIRST + 1)
+#define MANAGERVIEW_GET_STATUS_BAR(/*HWND*/ _hwnd)\
+ ((HWND)SendMessageW((_hwnd), MANAGERVIEW_WM_STATUSBAR, 0, 0L))
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_MANAGER_VIEW_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/ml_devices.rc b/Src/Plugins/Library/ml_devices/ml_devices.rc
new file mode 100644
index 00000000..436157f5
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/ml_devices.rc
@@ -0,0 +1,172 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "afxres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""afxres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "#include ""version.rc2""\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_MANAGER_VIEW DIALOGEX 0, 0, 317, 186
+STYLE DS_SETFONT | DS_NOIDLEMSG | DS_FIXEDSYS | DS_NOFAILCREATE | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_CONTROLPARENT | WS_EX_NOACTIVATE
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ PUSHBUTTON "Discover",IDC_BUTTON_DISCOVER,0,172,50,14
+ CONTROL "",IDC_SLIDER_ZOOM,"msctls_trackbar32",TBS_NOTICKS | TBS_ENABLESELRANGE | NOT WS_VISIBLE | WS_TABSTOP,244,174,73,12
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Menu
+//
+
+IDR_PLUGIN_MENU MENU
+BEGIN
+ POPUP "NavigationRootContext"
+ BEGIN
+ MENUITEM "&Open", ID_VIEW_OPEN
+ MENUITEM SEPARATOR
+ MENUITEM "Start &Discovery", ID_DISCOVERY_BEGIN
+ MENUITEM SEPARATOR
+ MENUITEM "&Help", ID_PLUGIN_HELP
+ END
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_WELCOMEWIDGET_TITLE "WINAMP DEVICE MANAGER"
+ IDS_WELCOMEWIDGET_TEXT "Manage the media on your devices by connecting them to Winamp. To get started, connect a device or click Discover to find already connected devices. To manage Android devices over Wi-Fi, ensure wireless sync is enabled in the Winamp App settings."
+ IDS_DEVICES_NAVIGATION_NODE "Devices"
+ IDS_CATEGORY_ATTACHED "Attached"
+ IDS_CATEGORY_DISCOVERED "Discovered"
+ IDS_INFOWIDGET_TITLE "Winamp Device Manager"
+ IDS_DEVICE_SERVICE_NOT_FOUND
+ "Winamp Device Manager unable to communicate with device service. Try reinstalling application."
+ IDS_CREATE_DEVICE_VIEW_FAILED
+ "Error creating device view. Verify that you have latest version of portable media plug-in."
+ IDS_SYNC_COMMAND_TITLE "&Sync"
+ IDS_SYNC_COMMAND_DESC "Transfer media to device"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_PLUGIN_NAME "Nullsoft Device Manager v%d.%02d"
+ 65535 "{CA4D071B-4E9B-44fd-862A-783FC763B63D}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_CANCEL_SYNC_COMMAND_TITLE "C&ancel Sync"
+ IDS_CANCEL_SYNC_COMMAND_DESC "Cancel synchronization"
+ IDS_CREATE_MANAGER_VIEW_FAILED "Unable to create device manager view."
+ IDS_ATTACH_COMMAND_TITLE "&Attach"
+ IDS_ATTACH_COMMAND_DESC "Connect device to Winamp"
+ IDS_DETACH_COMMAND_TITLE "&Detach"
+ IDS_DETACH_COMMAND_DESC "Detach device from Winamp"
+ IDS_EJECT_COMMAND_TITLE "&Eject"
+ IDS_EJECT_COMMAND_DESC "Eject device"
+ IDS_DEVICE_TYPE_SHORT "Type"
+ IDS_DEVICE_CONNECTION_SHORT "Connection"
+ IDS_TOTAL_SPACE "Total"
+ IDS_FREE_SPACE "Free"
+ IDS_USED_SPACE "Used"
+ IDS_FRACTION_SEPARATOR "."
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DEVICE_ACTIVITY_SHORT "Status"
+ IDS_PLUGIN_HELP_URL "https://help.winamp.com/hc/articles/8106455294612-Winamp-Portables-Guide"
+ IDS_RENAME_COMMAND_TITLE "&Rename\tF2"
+ IDS_RENAME_COMMAND_DESC "Change the name of this device."
+ IDS_VIEW_OPEN_COMMAND_TITLE "&Open"
+ IDS_VIEW_OPEN_COMMAND_DESC "Show contents of this device."
+ IDS_PREFERENCES_COMMAND_TITLE "&Preferences"
+ IDS_PREFERENCES_COMMAND_DESC "Change settings for this device."
+ IDS_PLAYLIST_CREATE_COMMAND_TITLE "&New Playlist"
+ IDS_PLAYLIST_CREATE_COMMAND_DESC "Make a new playlist on this device."
+ IDS_MESSAGEBOX_TITLE "Winamp Device Manager"
+ IDS_MESSAGE_UNABLE_TO_RENAME "Unable to change device name."
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DEVICE_MODEL_SHORT "Model"
+ IDS_DEVICE_STATUS_SHORT "Status"
+ IDS_STATUS_DISCOVERY_ACTIVE "Discovering devices..."
+ IDS_STATUS_SPACE_TEMPLATE "%s free of %s"
+ IDS_CATEGORY_ATTACHED_EMPTY_TEXT
+ "No devices are attached, select a discovered device to attach."
+ IDS_WELCOMEWIDGET_HELP_URL
+ "https://help.winamp.com/hc/articles/8106455294612-Winamp-Portables-Guide"
+ IDS_HELP_LINK "Help"
+END
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+#include "version.rc2"
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/Src/Plugins/Library/ml_devices/ml_devices.sln b/Src/Plugins/Library/ml_devices/ml_devices.sln
new file mode 100644
index 00000000..368e87ff
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/ml_devices.sln
@@ -0,0 +1,51 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29613.14
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ml_devices", "ml_devices.vcxproj", "{07D24820-5624-4EC3-AAC6-288D20E039DE}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "devices", "..\devices\devices.vcxproj", "{06F6E796-653F-48A9-BA2F-46B679D35F9E}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gen_deviceprovider", "gen_deviceprovider\gen_deviceprovider.vcxproj", "{A3C7D830-CD01-4C2B-9E1A-FB62B5236BA9}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Debug|x64 = Debug|x64
+ Release|Win32 = Release|Win32
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {07D24820-5624-4EC3-AAC6-288D20E039DE}.Debug|Win32.ActiveCfg = Debug|Win32
+ {07D24820-5624-4EC3-AAC6-288D20E039DE}.Debug|Win32.Build.0 = Debug|Win32
+ {07D24820-5624-4EC3-AAC6-288D20E039DE}.Debug|x64.ActiveCfg = Debug|x64
+ {07D24820-5624-4EC3-AAC6-288D20E039DE}.Debug|x64.Build.0 = Debug|x64
+ {07D24820-5624-4EC3-AAC6-288D20E039DE}.Release|Win32.ActiveCfg = Release|Win32
+ {07D24820-5624-4EC3-AAC6-288D20E039DE}.Release|Win32.Build.0 = Release|Win32
+ {07D24820-5624-4EC3-AAC6-288D20E039DE}.Release|x64.ActiveCfg = Release|x64
+ {07D24820-5624-4EC3-AAC6-288D20E039DE}.Release|x64.Build.0 = Release|x64
+ {06F6E796-653F-48A9-BA2F-46B679D35F9E}.Debug|Win32.ActiveCfg = Debug|Win32
+ {06F6E796-653F-48A9-BA2F-46B679D35F9E}.Debug|Win32.Build.0 = Debug|Win32
+ {06F6E796-653F-48A9-BA2F-46B679D35F9E}.Debug|x64.ActiveCfg = Debug|x64
+ {06F6E796-653F-48A9-BA2F-46B679D35F9E}.Debug|x64.Build.0 = Debug|x64
+ {06F6E796-653F-48A9-BA2F-46B679D35F9E}.Release|Win32.ActiveCfg = Release|Win32
+ {06F6E796-653F-48A9-BA2F-46B679D35F9E}.Release|Win32.Build.0 = Release|Win32
+ {06F6E796-653F-48A9-BA2F-46B679D35F9E}.Release|x64.ActiveCfg = Release|x64
+ {06F6E796-653F-48A9-BA2F-46B679D35F9E}.Release|x64.Build.0 = Release|x64
+ {A3C7D830-CD01-4C2B-9E1A-FB62B5236BA9}.Debug|Win32.ActiveCfg = Debug|Win32
+ {A3C7D830-CD01-4C2B-9E1A-FB62B5236BA9}.Debug|Win32.ABuild = Debug|Win32
+ {A3C7D830-CD01-4C2B-9E1A-FB62B5236BA9}.Debug|x64.ActiveCfg = Debug|x64
+ {A3C7D830-CD01-4C2B-9E1A-FB62B5236BA9}.Debug|x64.ABuild = Debug|x64
+ {A3C7D830-CD01-4C2B-9E1A-FB62B5236BA9}.Release|Win32.ActiveCfg = Release|Win32
+ {A3C7D830-CD01-4C2B-9E1A-FB62B5236BA9}.Release|Win32.Build = Release|Win32
+ {A3C7D830-CD01-4C2B-9E1A-FB62B5236BA9}.Release|x64.ActiveCfg = Release|x64
+ {A3C7D830-CD01-4C2B-9E1A-FB62B5236BA9}.Release|x64.Build = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {6DCE600E-002D-459F-9DDB-A21A22F89EF7}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_devices/ml_devices.vcxproj b/Src/Plugins/Library/ml_devices/ml_devices.vcxproj
new file mode 100644
index 00000000..baabc009
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/ml_devices.vcxproj
@@ -0,0 +1,412 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{07D24820-5624-4EC3-AAC6-288D20E039DE}</ProjectGuid>
+ <RootNamespace>ml_devices</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <IncludePath>$(IncludePath)</IncludePath>
+ <LibraryPath>$(LibraryPath)</LibraryPath>
+ <EmbedManifest>true</EmbedManifest>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <EmbedManifest>true</EmbedManifest>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <IncludePath>$(IncludePath)</IncludePath>
+ <LibraryPath>$(LibraryPath)</LibraryPath>
+ <EmbedManifest>true</EmbedManifest>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <EmbedManifest>true</EmbedManifest>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg">
+ <VcpkgEnableManifest>false</VcpkgEnableManifest>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..;..\..\..\;..\..\..\wasabi;..\..\..\agave;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_DEBUG;_WINDOWS;_USRDLL;ML_DEVICES_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4312;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <Profile>false</Profile>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ </Link>
+ <Xdcmake>
+ <OutputFile>$(IntDir)$(TargetName).xml</OutputFile>
+ </Xdcmake>
+ <Bscmake>
+ <OutputFile>$(IntDir)$(TargetName).bsc</OutputFile>
+ </Bscmake>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\
+xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..;../wasabi;../agave;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_DEBUG;_WINDOWS;_USRDLL;ML_DEVICES_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4312;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <Profile>false</Profile>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ </Link>
+ <Xdcmake>
+ <OutputFile>$(IntDir)$(TargetName).xml</OutputFile>
+ </Xdcmake>
+ <Bscmake>
+ <OutputFile>$(IntDir)$(TargetName).bsc</OutputFile>
+ </Bscmake>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\
+xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..;..\..\..\;..\..\..\wasabi;..\..\..\agave;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;NDEBUG;_WINDOWS;_USRDLL;ML_DEVICES_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4312;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <Xdcmake>
+ <OutputFile>$(IntDir)$(ProjectName).xml</OutputFile>
+ </Xdcmake>
+ <Bscmake>
+ <OutputFile>$(IntDir)$(TargetName).bsc</OutputFile>
+ </Bscmake>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..;..\..\..\;..\..\..\wasabi;..\..\..\agave;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;NDEBUG;_WINDOWS;_USRDLL;ML_DEVICES_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4312;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ModuleDefinitionFile>
+ </ModuleDefinitionFile>
+ </Link>
+ <Xdcmake>
+ <OutputFile>$(IntDir)$(TargetName).xml</OutputFile>
+ </Xdcmake>
+ <Bscmake>
+ <OutputFile>$(IntDir)$(TargetName).bsc</OutputFile>
+ </Bscmake>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp" />
+ <ClCompile Include="..\..\..\nu\trace.cpp" />
+ <ClCompile Include="backBuffer.cpp" />
+ <ClCompile Include="config.cpp" />
+ <ClCompile Include="deviceCommands.cpp" />
+ <ClCompile Include="deviceHandler.cpp" />
+ <ClCompile Include="deviceManagerHandler.cpp" />
+ <ClCompile Include="embeddedEditor.cpp" />
+ <ClCompile Include="eventRelay.cpp" />
+ <ClCompile Include="fillRegion.cpp" />
+ <ClCompile Include="graphics.cpp" />
+ <ClCompile Include="image.cpp" />
+ <ClCompile Include="imageCache.cpp" />
+ <ClCompile Include="infoWidget.cpp" />
+ <ClCompile Include="listWidget.cpp" />
+ <ClCompile Include="listWidgetCategory.cpp" />
+ <ClCompile Include="listWidgetCommand.cpp" />
+ <ClCompile Include="listWidgetConnection.cpp" />
+ <ClCompile Include="listWidgetGroup.cpp" />
+ <ClCompile Include="listWidgetItem.cpp" />
+ <ClCompile Include="listWidgetPaint.cpp" />
+ <ClCompile Include="listWidgetTooltip.cpp" />
+ <ClCompile Include="local_menu.cpp" />
+ <ClCompile Include="main.cpp" />
+ <ClCompile Include="managerView.cpp" />
+ <ClCompile Include="navigation.cpp" />
+ <ClCompile Include="navigationIcons.cpp" />
+ <ClCompile Include="plugin.cpp" />
+ <ClCompile Include="statusBar.cpp" />
+ <ClCompile Include="strings.cpp" />
+ <ClCompile Include="wasabi.cpp" />
+ <ClCompile Include="welcomeWidget.cpp" />
+ <ClCompile Include="widget.cpp" />
+ <ClCompile Include="widgetHost.cpp" />
+ <ClCompile Include="widgetStyle.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\General\gen_ml\menu.h" />
+ <ClInclude Include="..\..\..\nu\trace.h" />
+ <ClInclude Include="backBuffer.h" />
+ <ClInclude Include="common.h" />
+ <ClInclude Include="config.h" />
+ <ClInclude Include="deviceCommands.h" />
+ <ClInclude Include="deviceHandler.h" />
+ <ClInclude Include="deviceManagerHandler.h" />
+ <ClInclude Include="embeddedEditor.h" />
+ <ClInclude Include="eventRelay.h" />
+ <ClInclude Include="fillRegion.h" />
+ <ClInclude Include="graphics.h" />
+ <ClInclude Include="image.h" />
+ <ClInclude Include="imageCache.h" />
+ <ClInclude Include="infoWidget.h" />
+ <ClInclude Include="listWidget.h" />
+ <ClInclude Include="listWidgetInternal.h" />
+ <ClInclude Include="local_menu.h" />
+ <ClInclude Include="main.h" />
+ <ClInclude Include="managerView.h" />
+ <ClInclude Include="navigation.h" />
+ <ClInclude Include="navigationIcons.h" />
+ <ClInclude Include="plugin.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="statusBar.h" />
+ <ClInclude Include="strings.h" />
+ <ClInclude Include="wasabi.h" />
+ <ClInclude Include="welcomeWidget.h" />
+ <ClInclude Include="widget.h" />
+ <ClInclude Include="widgetHost.h" />
+ <ClInclude Include="widgetStyle.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\action-bg.png" />
+ <Image Include="resources\arrows.png" />
+ <Image Include="resources\attach-command-large.png" />
+ <Image Include="resources\attach-command-small.png" />
+ <Image Include="resources\cancel-sync-command-small.png" />
+ <Image Include="resources\command-bg.png" />
+ <Image Include="resources\command-secondary-bg.png" />
+ <Image Include="resources\detach-command-large.png" />
+ <Image Include="resources\detach-command-small.png" />
+ <Image Include="resources\devices-title-en.png" />
+ <Image Include="resources\devices.png" />
+ <Image Include="resources\eject-command-small.png" />
+ <Image Include="resources\generic-device-160x160.png" />
+ <Image Include="resources\generic-device-16x16.png" />
+ <Image Include="resources\item-hover.png" />
+ <Image Include="resources\item-select.png" />
+ <Image Include="resources\progress-large.png" />
+ <Image Include="resources\progress-small.png" />
+ <Image Include="resources\spacebar.png" />
+ <Image Include="resources\sync-command-large.png" />
+ <Image Include="resources\sync-command-small.png" />
+ <Image Include="resources\unknown-command-large.png" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_devices.rc" />
+ <ResourceCompile Include="png.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj">
+ <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/ml_devices.vcxproj.filters b/Src/Plugins/Library/ml_devices/ml_devices.vcxproj.filters
new file mode 100644
index 00000000..20529cc7
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/ml_devices.vcxproj.filters
@@ -0,0 +1,310 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="backBuffer.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="config.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="deviceCommands.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="deviceHandler.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="deviceManagerHandler.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="embeddedEditor.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="eventRelay.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="fillRegion.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="graphics.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="image.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="imageCache.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="infoWidget.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="listWidget.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="listWidgetCategory.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="listWidgetCommand.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="listWidgetConnection.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="listWidgetGroup.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="listWidgetItem.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="listWidgetPaint.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="listWidgetTooltip.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="local_menu.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="managerView.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="navigation.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="navigationIcons.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="plugin.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="statusBar.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="strings.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="wasabi.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="welcomeWidget.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="widgetStyle.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="widget.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="widgetHost.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\PtrList.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\trace.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="backBuffer.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="common.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="config.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="deviceCommands.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="deviceHandler.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="deviceManagerHandler.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="embeddedEditor.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="eventRelay.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="fillRegion.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="graphics.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="image.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="imageCache.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="infoWidget.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="listWidget.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="listWidgetInternal.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="local_menu.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="managerView.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="navigation.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="navigationIcons.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="plugin.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="statusBar.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="strings.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="wasabi.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="welcomeWidget.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="widget.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="widgetHost.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="widgetStyle.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\menu.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\trace.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_devices.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ <ResourceCompile Include="png.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\attach-command-small.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\attach-command-large.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\cancel-sync-command-small.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\command-secondary-bg.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\detach-command-large.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\detach-command-small.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\generic-device-16x16.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\devices-title-en.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\devices.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\eject-command-small.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\command-bg.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\generic-device-160x160.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\action-bg.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\arrows.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\item-select.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\item-hover.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\progress-small.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\progress-large.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\sync-command-large.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\sync-command-small.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\spacebar.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\unknown-command-large.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{e0dd5a27-571d-4f70-a2d4-d5752e61b4c9}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{517e7a4b-9342-4d4c-8b09-cc9cd316f8d2}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{b0bc738e-6b68-4e58-9192-30fd269eb5b3}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Image Files">
+ <UniqueIdentifier>{9918fa42-7a6e-4012-93e5-82c60aac295f}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\gen_ml">
+ <UniqueIdentifier>{f48e1848-fef5-4f67-a582-5621c5ca6e47}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\nu">
+ <UniqueIdentifier>{4b166e51-c75c-4c33-81ff-81af63bdcbfe}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\gen_ml">
+ <UniqueIdentifier>{1a9a3d9f-f9f0-4c01-b873-2f961be3f40f}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\nu">
+ <UniqueIdentifier>{5960b4c7-3d94-4269-9755-8cabb8309b36}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/navigation.cpp b/Src/Plugins/Library/ml_devices/navigation.cpp
new file mode 100644
index 00000000..25fade94
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/navigation.cpp
@@ -0,0 +1,1073 @@
+#include "main.h"
+#include "./navigation.h"
+
+#include <strsafe.h>
+
+#define DEVICES_NAVITEM_PREFIX L"ml_devices_"
+
+static HNAVITEM navigationRoot = NULL;
+static size_t deviceHandler = 0;
+
+static HNAVITEM
+NavigationItem_Insert(HNAVITEM parent,
+ HNAVITEM insertAfter,
+ unsigned int mask,
+ int itemId,
+ const wchar_t *name,
+ const wchar_t *displayName,
+ unsigned int style,
+ int image,
+ LPARAM param)
+{
+ NAVITEM *item;
+ NAVINSERTSTRUCT nis = {0};
+
+ nis.hInsertAfter = insertAfter;
+ nis.hParent = parent;
+ item = &nis.item;
+
+ item->cbSize = sizeof(NAVITEM);
+ item->id = itemId;
+
+ if (0 != (NIMF_IMAGE & mask))
+ mask |= NIMF_IMAGESEL;
+
+ item->mask = mask;
+ item->pszText = (wchar_t*)displayName;
+ item->pszInvariant = (wchar_t*)name;
+ item->style = style;
+ item->styleMask = style;
+ item->iImage = image;
+ item->iSelectedImage = image;
+ item->lParam = param;
+
+ if (!wcsnicmp(L"ml_devices_all_sources", item->pszInvariant, 22))
+ {
+ // and this will allow us to make the cloud library
+ // root do a web page or a sources view as needed...
+ return NULL;
+ }
+
+ return MLNavCtrl_InsertItem(Plugin_GetLibraryWindow(), &nis);
+}
+
+static BOOL
+NavigationItem_Delete(HNAVITEM item)
+{
+ return MLNavCtrl_DeleteItem(Plugin_GetLibraryWindow(), item);
+}
+
+static HNAVITEM
+NavigationItem_GetFromMessage(INT msg, INT_PTR param)
+{
+ return (msg < ML_MSG_NAVIGATION_FIRST) ?
+ MLNavCtrl_FindItemById(Plugin_GetLibraryWindow(), param) :
+ (HNAVITEM)param;
+}
+
+static HNAVITEM
+NavigationItem_Find(HNAVITEM root, const wchar_t *name, BOOL allow_root = 0)
+{
+ NAVCTRLFINDPARAMS find = {0};
+ HNAVITEM item;
+ HWND libraryWindow;
+
+ if (NULL == name)
+ return NULL;
+
+ libraryWindow = Plugin_GetLibraryWindow();
+ if (NULL == libraryWindow)
+ return NULL;
+
+ find.pszName = (wchar_t*)name;
+ find.cchLength = -1;
+ find.compFlags = NICF_INVARIANT;
+ find.fFullNameSearch = FALSE;
+
+ item = MLNavCtrl_FindItemByName(libraryWindow, &find);
+ if (NULL == item)
+ return NULL;
+
+ if (!allow_root)
+ {
+ // if allowed then we can look for root level items which
+ // is really for getting 'cloud' devices to another group
+ if (NULL != root &&
+ root != MLNavItem_GetParent(libraryWindow, item))
+ {
+ item = NULL;
+ }
+ }
+
+ return item;
+}
+
+static wchar_t *
+NavigationItem_GetNameFromDeviceName(wchar_t *buffer, size_t bufferMax, const char *name)
+{
+ wchar_t *cursor;
+ size_t length;
+ BOOL allocated;
+
+ if (NULL == name || '\0' == *name)
+ return NULL;
+
+ length = MultiByteToWideChar(CP_UTF8, 0, name, -1, NULL, 0);
+ if (ARRAYSIZE(DEVICES_NAVITEM_PREFIX) > bufferMax ||
+ length > (bufferMax - (ARRAYSIZE(DEVICES_NAVITEM_PREFIX) - 1)))
+ {
+ bufferMax = length + ARRAYSIZE(DEVICES_NAVITEM_PREFIX) - 1;
+ buffer = String_Malloc(bufferMax);
+ if (NULL == buffer)
+ return NULL;
+
+ allocated = TRUE;
+ }
+ else
+ allocated = FALSE;
+
+ if (FAILED(StringCchCopyEx(buffer, bufferMax, DEVICES_NAVITEM_PREFIX, &cursor, &length, 0)) ||
+ 0 == MultiByteToWideChar(CP_UTF8, 0, name, -1, cursor, (int)length))
+ {
+ if (FALSE != allocated)
+ String_Free(buffer);
+
+ return NULL;
+ }
+
+ return buffer;
+}
+
+static HNAVITEM
+NavigationItem_FindFromDeviceName(HNAVITEM root, const char *name)
+{
+ wchar_t buffer[1], *itemName;
+ HNAVITEM item;
+
+ if (NULL == root)
+ return NULL;
+
+ itemName = NavigationItem_GetNameFromDeviceName(buffer, ARRAYSIZE(buffer), name);
+ if (NULL == itemName)
+ return NULL;
+
+ item = NavigationItem_Find(root, itemName);
+
+ if (itemName != buffer)
+ String_Free(itemName);
+
+ return item;
+}
+
+static HNAVITEM
+NavigationItem_FindFromDevice(HNAVITEM root, ifc_device *device)
+{
+ if (NULL == device)
+ return NULL;
+
+ return NavigationItem_FindFromDeviceName(root, device->GetName());
+}
+
+static BOOL
+NavigationItem_DisplayDeviceMenu(HNAVITEM item, const char *deviceName, HWND hostWindow, POINT pt)
+{
+ HMENU menu;
+ ifc_device *device;
+ unsigned int commandId;
+ BOOL succeeded;
+ HWND libraryWindow;
+
+ libraryWindow = Plugin_GetLibraryWindow();
+
+ if (NULL == item|| NULL == deviceName)
+ return FALSE;
+
+ if (NULL == WASABI_API_DEVICES ||
+ S_OK != WASABI_API_DEVICES->DeviceFind(deviceName, &device))
+ {
+ return FALSE;
+ }
+
+ menu = CreatePopupMenu();
+ if (NULL != menu)
+ {
+ if (0 == Menu_InsertDeviceItems(menu, 0, 100, device, DeviceCommandContext_NavigationMenu))
+ {
+ DestroyMenu(menu);
+ menu = NULL;
+ }
+ }
+
+ device->Release();
+
+ if (NULL == menu)
+ return FALSE;
+
+ succeeded = FALSE;
+
+ if (item == MLNavCtrl_GetSelection(libraryWindow))
+ {
+ commandId = Menu_FindItemByAnsiStringData(menu, "view_open");
+ if ((unsigned int)-1 != commandId)
+ EnableMenuItem(menu, commandId, MF_BYCOMMAND | MF_DISABLED);
+ }
+
+ commandId = Menu_TrackPopup(Plugin_GetLibraryWindow(), menu,
+ TPM_LEFTALIGN | TPM_TOPALIGN | TPM_VERPOSANIMATION | TPM_VERTICAL | TPM_RETURNCMD,
+ pt.x, pt.y, hostWindow, NULL);
+
+ if (0 != commandId)
+ {
+ const char *command;
+
+ command = (const char*)Menu_GetItemData(menu, commandId, FALSE);
+ if (NULL != command)
+ {
+ if (NULL != WASABI_API_DEVICES &&
+ S_OK == WASABI_API_DEVICES->DeviceFind(deviceName, &device))
+ {
+ BOOL commandProcessed;
+
+ commandProcessed = FALSE;
+
+ if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, 0, command, -1, "view_open", -1))
+ {
+ succeeded = MLNavItem_Select(libraryWindow, item);
+ commandProcessed = succeeded;
+ }
+ else if (CSTR_EQUAL == CompareStringA(CSTR_INVARIANT, 0, command, -1, "rename", -1))
+ {
+ succeeded = MLNavItem_EditTitle(libraryWindow, item);
+ commandProcessed = succeeded;
+ }
+
+ if (FALSE == commandProcessed &&
+ SUCCEEDED(device->SendCommand(command, hostWindow, 0)))
+ {
+ succeeded = TRUE;
+ }
+
+ device->Release();
+ }
+ }
+ }
+ else
+ {
+ if (ERROR_SUCCESS == GetLastError())
+ succeeded = TRUE;
+ }
+
+ Menu_FreeItemData(menu, 0, -1);
+
+ return succeeded;
+}
+
+static BOOL
+NavigationItem_DisplayRootMenu(HNAVITEM item, HWND hostWindow, POINT pt)
+{
+ HMENU pluginMenu, menu;
+ BOOL succeeded;
+
+ if (NULL == item)
+ return FALSE;
+
+ pluginMenu = Plugin_LoadMenu();
+ if (NULL == pluginMenu)
+ return FALSE;
+
+ succeeded = FALSE;
+
+ menu = GetSubMenu(pluginMenu, 0);
+ if (NULL != menu)
+ {
+ HWND libraryWindow;
+ HNAVITEM selectedItem;
+
+ libraryWindow = Plugin_GetLibraryWindow();
+
+ selectedItem = MLNavCtrl_GetSelection(libraryWindow);
+
+ EnableMenuItem(menu, ID_VIEW_OPEN, MF_BYCOMMAND | ((item != selectedItem) ? MF_ENABLED : MF_DISABLED));
+ SetMenuDefaultItem(menu, ID_VIEW_OPEN, FALSE);
+
+ unsigned int commandId = Menu_TrackPopup(Plugin_GetLibraryWindow(), menu,
+ TPM_LEFTALIGN | TPM_TOPALIGN | TPM_VERPOSANIMATION | TPM_VERTICAL | TPM_RETURNCMD,
+ pt.x, pt.y, hostWindow, NULL);
+
+ if (0 != commandId)
+ {
+ switch(commandId)
+ {
+ case ID_VIEW_OPEN:
+ MLNavItem_Select(libraryWindow, item);
+ break;
+ case ID_DISCOVERY_BEGIN:
+ Plugin_BeginDiscovery();
+ break;
+ case ID_PLUGIN_HELP:
+ Plugin_ShowHelp();
+ break;
+ }
+ }
+ else
+ {
+ if (ERROR_SUCCESS == GetLastError())
+ succeeded = TRUE;
+ }
+ }
+
+ DestroyMenu(pluginMenu);
+
+ return succeeded;
+}
+
+static BOOL
+Navigation_GetDeviceTitle(ifc_device *device, wchar_t *buffer, size_t bufferMax)
+{
+ size_t read, write;
+
+ if (NULL == device ||
+ FAILED(device->GetDisplayName(buffer, bufferMax)))
+ {
+ return FALSE;
+ }
+
+ for(read = 0, write = 0;; read++)
+ {
+ if (read == bufferMax)
+ return FALSE;
+
+ if (L'\r' == buffer[read])
+ continue;
+
+ if (L'\n' == buffer[read] ||
+ L'\t' == buffer[read] ||
+ L'\b' == buffer[read])
+ {
+ buffer[write] = L' ';
+ }
+
+ buffer[write] = buffer[read];
+ if (L'\0' == buffer[read])
+ break;
+
+ write++;
+ }
+
+ return TRUE;
+}
+
+static BOOL
+Navigation_DeviceAdd(HNAVITEM root, ifc_device *device)
+{
+ HNAVITEM item, insertAfter = NCI_LAST;
+ wchar_t nameBuffer[256] = {0}, *itemName;
+ wchar_t title[512] = {0};
+ int iconIndex;
+
+ if (NULL == device)
+ return FALSE;
+
+ itemName = NavigationItem_GetNameFromDeviceName(nameBuffer, ARRAYSIZE(nameBuffer), device->GetName());
+ if (NULL == itemName)
+ return FALSE;
+
+ if (NULL != NavigationItem_Find(root, itemName))
+ {
+ if (itemName != nameBuffer)
+ String_Free(itemName);
+
+ return FALSE;
+ }
+
+ if (FALSE == Navigation_GetDeviceTitle(device, title, ARRAYSIZE(title)))
+ title[0] = L'\0';
+
+ iconIndex = NavigationIcons_GetDeviceIconIndex(device);
+
+ // filter the cloud devices to their own group
+ if (!lstrcmpiA(device->GetConnection(), "cloud"))
+ {
+ HNAVITEM cloud = NavigationItem_Find(navigationRoot, L"cloud_sources", TRUE);
+ if (cloud != NULL)
+ {
+ root = cloud;
+ HNAVITEM transfers = NavigationItem_Find(0, L"cloud_transfers", TRUE);
+
+ // to maintain some specific orders, we need to alter the insert position
+ if (!wcsnicmp(L"ml_devices_hss", itemName, 14))
+ {
+ insertAfter = transfers;
+ if (!insertAfter) insertAfter = NCI_LAST;
+ }
+ else if (!wcsnicmp(L"ml_devices_local_desktop", itemName, 24))
+ {
+ insertAfter = NavigationItem_Find(0, L"ml_devices_hss", TRUE);
+ if (!insertAfter) insertAfter = NCI_LAST;
+ }
+
+ // when adding children, change from the cloud source to the open/closed arrow
+ HWND libraryWindow = Plugin_GetLibraryWindow();
+ if (NULL != libraryWindow && transfers)
+ {
+ NAVITEM itemInfo = {0};
+ itemInfo.hItem = cloud;
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.mask = NIMF_IMAGE | NIMF_IMAGESEL;
+ itemInfo.iImage = itemInfo.iSelectedImage = -1;
+ MLNavItem_SetInfo(libraryWindow, &itemInfo);
+ }
+ }
+ }
+
+ item = NavigationItem_Insert(root, insertAfter,
+ NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL | NIMF_STYLE,
+ 0, itemName, title, NIS_ALLOWEDIT, iconIndex, 0L);
+
+ if (NULL == item)
+ NavigationIcons_ReleaseIconIndex(iconIndex);
+
+ if (itemName != nameBuffer)
+ String_Free(itemName);
+
+ if (NULL == item)
+ return FALSE;
+
+ device->SetNavigationItem(item);
+
+ return TRUE;
+}
+
+static BOOL
+Navigation_DeviceRemove(HNAVITEM root, ifc_device *device)
+{
+ HNAVITEM item;
+
+ item = NavigationItem_FindFromDevice(root, device);
+ if (NULL == item)
+ return FALSE;
+
+ return NavigationItem_Delete(item);
+}
+
+static BOOL
+Navigation_DeviceTitleChanged(HNAVITEM root, ifc_device *device)
+{
+ NAVITEM itemInfo;
+ wchar_t buffer[1024] = {0};
+
+ itemInfo.hItem = NavigationItem_FindFromDevice(root, device);
+ if (NULL == itemInfo.hItem)
+ return FALSE;
+
+ if (FALSE == Navigation_GetDeviceTitle(device, buffer, ARRAYSIZE(buffer)))
+ return FALSE;
+
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.pszText = buffer;
+ itemInfo.cchTextMax = -1;
+ itemInfo.mask = NIMF_TEXT;
+
+ return MLNavItem_SetInfo(Plugin_GetLibraryWindow(), &itemInfo);
+}
+
+static BOOL
+Navigation_DeviceIconChanged(HNAVITEM root, ifc_device *device)
+{
+ NAVITEM itemInfo;
+ int iconIndex;
+ HWND libraryWindow;
+
+ if (NULL == root || NULL == device)
+ return FALSE;
+
+ libraryWindow = Plugin_GetLibraryWindow();
+
+ itemInfo.hItem = NavigationItem_FindFromDevice(root, device);
+ if (NULL == itemInfo.hItem)
+ return FALSE;
+
+ iconIndex = NavigationIcons_GetDeviceIconIndex(device);
+
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.mask = NIMF_IMAGE;
+
+ if (FALSE == MLNavItem_GetInfo(libraryWindow, &itemInfo))
+ {
+ NavigationIcons_ReleaseIconIndex(iconIndex);
+ return FALSE;
+ }
+
+ if (itemInfo.iImage == iconIndex)
+ {
+ NavigationIcons_ReleaseIconIndex(iconIndex);
+ return TRUE;
+ }
+
+ NavigationIcons_ReleaseIconIndex(itemInfo.iImage);
+ itemInfo.mask = NIMF_IMAGE | NIMF_IMAGESEL;
+ itemInfo.iImage = iconIndex;
+ itemInfo.iSelectedImage = iconIndex;
+
+ if (FALSE == MLNavItem_SetInfo(libraryWindow, &itemInfo))
+ {
+ NavigationIcons_ReleaseIconIndex(iconIndex);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+Navigation_AddExistingDevices(HNAVITEM root)
+{
+ ifc_device *device;
+ ifc_deviceobject *object;
+ ifc_deviceobjectenum *enumerator;
+
+ if (NULL == WASABI_API_DEVICES)
+ return;
+
+ if (FAILED(WASABI_API_DEVICES->DeviceEnumerate(&enumerator)))
+ return;
+
+ while(S_OK == enumerator->Next(&object, 1, NULL))
+ {
+ if (SUCCEEDED(object->QueryInterface(IFC_Device, (void**)&device)))
+ {
+ if (FALSE == device->GetHidden() &&
+ FALSE != device->GetAttached())
+ {
+ Navigation_DeviceAdd(root, device);
+ }
+ device->Release();
+ }
+ object->Release();
+ }
+ enumerator->Release();
+}
+
+static void
+Navigation_DeviceCb(ifc_device *device, DeviceEvent eventId, void *user)
+{
+ HNAVITEM rootItem;
+ rootItem = (HNAVITEM)user;
+
+ switch(eventId)
+ {
+ case Event_DeviceAdded:
+ if (FALSE != device->GetAttached())
+ Navigation_DeviceAdd(rootItem, device);
+ break;
+ case Event_DeviceRemoved:
+ if (FALSE != device->GetAttached())
+ Navigation_DeviceRemove(rootItem, device);
+ break;
+ case Event_DeviceHidden:
+ if (FALSE != device->GetAttached())
+ Navigation_DeviceRemove(rootItem, device);
+ break;
+ case Event_DeviceShown:
+ if (FALSE != device->GetAttached())
+ Navigation_DeviceAdd(rootItem, device);
+ break;
+ case Event_DeviceAttached:
+ Navigation_DeviceAdd(rootItem, device);
+ break;
+ case Event_DeviceDetached:
+ Navigation_DeviceRemove(rootItem, device);
+ break;
+ case Event_DeviceDisplayNameChanged:
+ Navigation_DeviceTitleChanged(rootItem, device);
+ break;
+ case Event_DeviceIconChanged:
+ Navigation_DeviceIconChanged(rootItem, device);
+ break;
+ }
+}
+
+static BOOL
+Navigation_RegisterDeviceHandler(HNAVITEM root)
+{
+ HWND eventRelay;
+ DeviceEventCallbacks callbacks;
+
+ if (0 != deviceHandler)
+ return FALSE;
+
+ eventRelay = Plugin_GetEventRelayWindow();
+ if (NULL == eventRelay)
+ return FALSE;
+
+ ZeroMemory(&callbacks, sizeof(callbacks));
+ callbacks.deviceCb = Navigation_DeviceCb;
+
+ deviceHandler = EVENTRELAY_REGISTER_HANDLER(eventRelay, &callbacks, root);
+ return (0 != deviceHandler);
+}
+
+BOOL
+Navigation_Initialize(void)
+{
+ HWND libraryWindow;
+ wchar_t buffer[128] = {0};
+
+ if (NULL != navigationRoot)
+ return FALSE;
+
+ libraryWindow = Plugin_GetLibraryWindow();
+
+ MLNavCtrl_BeginUpdate(libraryWindow, NUF_LOCK_TOP);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_DEVICES_NAVIGATION_NODE, buffer, ARRAYSIZE(buffer));
+
+ navigationRoot = NavigationItem_Insert(NULL, NULL,
+ NIMF_ITEMID | NIMF_TEXT | NIMF_STYLE | NIMF_TEXTINVARIANT |
+ NIMF_PARAM | NIMF_IMAGE | NIMF_IMAGESEL,
+ ML_TREEVIEW_ID_DEVICES,
+ DEVICES_NAVITEM_PREFIX L"root",
+ buffer,
+ NIS_HASCHILDREN | NIS_ALLOWCHILDMOVE | NIS_DEFAULTIMAGE,
+ -1,
+ -1L);
+
+ if (NULL == navigationRoot)
+ return FALSE;
+
+ Navigation_AddExistingDevices(navigationRoot);
+ Navigation_RegisterDeviceHandler(navigationRoot);
+
+ MLNavCtrl_EndUpdate(libraryWindow);
+
+ return TRUE;
+}
+
+void
+Navigation_Uninitialize(void)
+{
+ if (0 != deviceHandler)
+ {
+ HWND eventRelay;
+ eventRelay = Plugin_GetEventRelayWindow();
+ if (NULL != eventRelay)
+ {
+ EVENTRELAY_UNREGISTER_HANDLER(eventRelay, deviceHandler);
+ }
+ deviceHandler = NULL;
+ }
+
+ if (NULL != navigationRoot)
+ {
+ NavigationItem_Delete(navigationRoot);
+ navigationRoot = FALSE;
+ }
+
+ NavigationIcons_ClearCache();
+}
+
+BOOL
+Navigation_SelectDevice(const char *name)
+{
+ HNAVITEM item;
+
+ item = NavigationItem_FindFromDeviceName(navigationRoot, name);
+ if (NULL == item)
+ return FALSE;
+
+ return MLNavItem_Select(Plugin_GetLibraryWindow(), item);
+}
+
+BOOL
+Navigation_EditDeviceTitle(const char *name)
+{
+ HNAVITEM item;
+
+ item = NavigationItem_FindFromDeviceName(navigationRoot, name);
+ if (NULL == item)
+ return FALSE;
+
+ return MLNavItem_EditTitle(Plugin_GetLibraryWindow(), item);
+}
+
+static void
+Navigation_DestroyCb()
+{
+}
+
+static BOOL
+NavigationItem_IsMine(HNAVITEM item, ifc_device **device)
+{
+ NAVITEM info;
+ wchar_t buffer[128] = {0};
+ INT nameLength;
+ INT prefixLength;
+
+ if (NULL == item)
+ return FALSE;
+
+ if (item == navigationRoot)
+ {
+ if (NULL != device)
+ *device = NULL;
+ return TRUE;
+ }
+
+ info.cbSize = sizeof(NAVITEM);
+ info.hItem = item;
+ info.mask = NIMF_TEXTINVARIANT;
+ info.cchInvariantMax = ARRAYSIZE(buffer);
+ info.pszInvariant = buffer;
+
+ if (FALSE == MLNavItem_GetInfo(Plugin_GetLibraryWindow(), &info))
+ return FALSE;
+
+ // to maintain some specific orders, we need to alter the insert position
+ BOOL swappped = FALSE;
+ if (!wcsnicmp(L"cloud_sources", info.pszInvariant, 13))
+ {
+ // and this will allow us to make the cloud library
+ // root do a web page or a sources view as needed...
+ lstrcpynW(info.pszInvariant, L"ml_devices_all_sources", ARRAYSIZE(buffer));
+ swappped = TRUE;
+ }
+
+ nameLength = (NULL != info.pszInvariant) ? lstrlen(info.pszInvariant) : 0;
+ prefixLength = ARRAYSIZE(DEVICES_NAVITEM_PREFIX) - 1;
+
+ if (nameLength <= prefixLength)
+ return FALSE;
+
+ if (CSTR_EQUAL != CompareString(CSTR_INVARIANT, 0,
+ DEVICES_NAVITEM_PREFIX, prefixLength, info.pszInvariant, prefixLength))
+ {
+ return FALSE;
+ }
+
+ if (NULL != device)
+ {
+ char name[ARRAYSIZE(buffer)] = {0};
+ nameLength = WideCharToMultiByte(CP_UTF8, 0,
+ info.pszInvariant + prefixLength, nameLength - prefixLength,
+ name, ARRAYSIZE(name), NULL, NULL);
+ name[nameLength] = '\0';
+
+ if (0 == nameLength ||
+ NULL == WASABI_API_DEVICES ||
+ S_OK != WASABI_API_DEVICES->DeviceFind(name, device))
+ {
+ *device = NULL;
+ if (swappped) return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static HWND
+Navigation_CreateViewErrorWidget(HWND hostWindow, void *user)
+{
+ return InfoWidget_CreateWindow(WIDGET_TYPE_VIEW_ERROR,
+ MAKEINTRESOURCE(IDS_INFOWIDGET_TITLE),
+ MAKEINTRESOURCE(IDS_CREATE_DEVICE_VIEW_FAILED),
+ NULL,
+ hostWindow, 0, 0, 0, 0, FALSE, 0);
+}
+
+static HWND
+NavigationItem_CreateViewCb(HNAVITEM item, HWND parentWindow)
+{
+ HWND hwnd;
+ ifc_device *device;
+
+ if (NULL == item ||
+ FALSE == NavigationItem_IsMine(item, &device))
+ return NULL;
+
+ if (NULL != device)
+ {
+ hwnd = device->CreateView(parentWindow);
+ device->Release();
+
+ if (NULL == hwnd)
+ {
+ hwnd = WidgetHost_Create(0, 0, 0, 0, 0, parentWindow, Navigation_CreateViewErrorWidget, NULL);
+ }
+ }
+ else
+ {
+ hwnd = ManagerView_CreateWindow(parentWindow);
+ }
+ return hwnd;
+}
+
+static BOOL
+NavigationItem_ShowContextMenuCb(HNAVITEM item, HWND hostWindow, POINTS pts)
+{
+ POINT pt;
+ ifc_device *device;
+
+ if (NULL == item ||
+ FALSE == NavigationItem_IsMine(item, &device))
+ return FALSE;
+
+ POINTSTOPOINT(pt, pts);
+
+ if (item != navigationRoot)
+ {
+ if (NULL != device)
+ {
+ char *deviceName = AnsiString_Duplicate(device->GetName());
+ device->Release();
+ device = NULL;
+
+ if (NULL != deviceName)
+ {
+ NavigationItem_DisplayDeviceMenu(item, deviceName, hostWindow, pt);
+ AnsiString_Free(deviceName);
+ }
+ }
+ }
+ else
+ {
+ NavigationItem_DisplayRootMenu(item, hostWindow, pt);
+ }
+
+ if (NULL != device)
+ device->Release();
+
+ return TRUE;
+}
+
+static BOOL
+NavigationItem_ShowHelpCb(HNAVITEM item, HWND hostWindow, POINTS pts)
+{
+ if (NULL == item ||
+ FALSE == NavigationItem_IsMine(item, NULL))
+ {
+ return FALSE;
+ }
+
+ Plugin_ShowHelp();
+ return TRUE;
+}
+
+static BOOL
+NavigationItem_DeleteCb(HNAVITEM item)
+{
+ ifc_device *device;
+
+ if (NULL == item ||
+ FALSE == NavigationItem_IsMine(item, &device))
+ return FALSE;
+
+ if (NULL != device)
+ {
+ device->SetNavigationItem(NULL);
+ device->Release();
+ }
+
+ if (item != navigationRoot)
+ {
+ NAVITEM itemInfo;
+ HWND libraryWindow;
+
+ libraryWindow = Plugin_GetLibraryWindow();
+
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.mask = NIMF_IMAGE;
+ itemInfo.hItem = item;
+
+ if (FALSE != MLNavItem_GetInfo(libraryWindow, &itemInfo) &&
+ -1 != itemInfo.iImage)
+ {
+ NavigationIcons_ReleaseIconIndex(itemInfo.iImage);
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL
+NavigationItem_KeyDownCb(HNAVITEM item, NMTVKEYDOWN *keyData)
+{
+ ifc_device *device;
+
+ if (NULL == keyData ||
+ NULL == item ||
+ FALSE == NavigationItem_IsMine(item, &device))
+ {
+ return FALSE;
+ }
+
+ if (NULL == device)
+ return TRUE;
+
+ switch(keyData->wVKey)
+ {
+ case VK_F2:
+ MLNavItem_EditTitle(Plugin_GetLibraryWindow(), item);
+ break;
+ }
+
+ device->Release();
+ return TRUE;
+}
+
+static int
+NavigationItem_DropTargetCb(HNAVITEM item, unsigned int dataType, void *data)
+{
+ ifc_device *device;
+ int result;
+
+ if (NULL == item ||
+ FALSE == NavigationItem_IsMine(item, &device))
+ {
+ return 0;
+ }
+
+ if (NULL == device)
+ return -1;
+
+ result = -1;
+
+ if (NULL == data)
+ {
+ if (S_OK == device->GetDropSupported(dataType))
+ result = 1;
+ }
+ else
+ {
+ if (SUCCEEDED(device->Drop(data, dataType)))
+ result = 1;
+ }
+
+ device->Release();
+
+ return result;
+}
+
+static BOOL
+NavigationItem_TitleEditBeginCb(HNAVITEM item)
+{ // return TRUE to cancel ediging (only on own items!!!);
+
+ ifc_device *device;
+
+ BOOL blockEditor;
+
+ if (NULL == item ||
+ FALSE == NavigationItem_IsMine(item, &device))
+ {
+ return FALSE;
+ }
+
+ if (NULL == device)
+ return TRUE;
+
+ blockEditor = (FALSE == DeviceCommand_GetEnabled(device, "rename",
+ DeviceCommandContext_NavigationMenu));
+
+ device->Release();
+
+ return blockEditor;
+}
+
+
+static BOOL
+NavigationItem_TitleEditEndCb(HNAVITEM item, const wchar_t *title)
+{
+ HRESULT hr;
+ ifc_device *device;
+
+ if (NULL == title)
+ return FALSE;
+
+ if (NULL == item ||
+ FALSE == NavigationItem_IsMine(item, &device) ||
+ NULL == device)
+ {
+ return FALSE;
+ }
+
+ hr = device->SetDisplayName(title);
+ device->Release();
+
+ if (FAILED(hr))
+ {
+ HWND libraryWindow;
+ wchar_t title[256] = {0}, message[1024] = {0};
+
+ libraryWindow = Plugin_GetLibraryWindow();
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_MESSAGEBOX_TITLE, title, ARRAYSIZE(title));
+ WASABI_API_LNGSTRINGW_BUF(IDS_MESSAGE_UNABLE_TO_RENAME, message, ARRAYSIZE(message));
+
+ MessageBox(libraryWindow, message, title, MB_OK | MB_ICONERROR);
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+
+BOOL
+Navigation_ProcessMessage(INT msg, INT_PTR param1, INT_PTR param2, INT_PTR param3, INT_PTR *result)
+{
+ if (msg == ML_MSG_NO_CONFIG)
+ {
+ *result = TRUE;
+ return TRUE;
+ }
+
+ if (msg < ML_MSG_TREE_BEGIN || msg > ML_MSG_TREE_END)
+ return FALSE;
+
+ HNAVITEM item;
+ switch(msg)
+ {
+ case ML_MSG_TREE_ONCREATEVIEW:
+ item = NavigationItem_GetFromMessage(msg, param1);
+ *result = (INT_PTR)NavigationItem_CreateViewCb(item, (HWND)param2);
+ return TRUE;
+
+ case ML_MSG_NAVIGATION_CONTEXTMENU:
+ item = NavigationItem_GetFromMessage(msg, param1);
+ *result = (INT_PTR)NavigationItem_ShowContextMenuCb(item, (HWND)param2, MAKEPOINTS(param3));
+ return TRUE;
+
+ case ML_MSG_NAVIGATION_HELP:
+ item = NavigationItem_GetFromMessage(msg, param1);
+ *result = (INT_PTR)NavigationItem_ShowHelpCb(item, (HWND)param2, MAKEPOINTS(param3));
+ return TRUE;
+
+ case ML_MSG_NAVIGATION_ONDELETE:
+ item = NavigationItem_GetFromMessage(msg, param1);
+ *result = (INT_PTR)NavigationItem_DeleteCb(item);
+ return TRUE;
+
+ case ML_MSG_TREE_ONKEYDOWN:
+ item = NavigationItem_GetFromMessage(msg, param1);
+ *result = (INT_PTR)NavigationItem_KeyDownCb(item, (NMTVKEYDOWN*)param2);
+ return TRUE;
+
+ case ML_MSG_TREE_ONDROPTARGET:
+ item = NavigationItem_GetFromMessage(msg, param1);
+ *result = (INT_PTR)NavigationItem_DropTargetCb(item, (unsigned int)param2, (void*)param3);
+ return TRUE;
+
+ case ML_MSG_NAVIGATION_ONBEGINTITLEEDIT:
+ item = NavigationItem_GetFromMessage(msg, param1);
+ *result = (INT_PTR)NavigationItem_TitleEditBeginCb(item);
+ return TRUE;
+
+ case ML_MSG_NAVIGATION_ONENDTITLEEDIT:
+ item = NavigationItem_GetFromMessage(msg, param1);
+ *result = (INT_PTR)NavigationItem_TitleEditEndCb(item, (const wchar_t*)param2);
+ return TRUE;
+
+ case ML_MSG_NAVIGATION_ONDESTROY:
+ Navigation_DestroyCb();
+ *result = 0L;
+ return TRUE;
+
+ }
+ return FALSE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/navigation.h b/Src/Plugins/Library/ml_devices/navigation.h
new file mode 100644
index 00000000..c7849ba7
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/navigation.h
@@ -0,0 +1,29 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_NAVIGATION_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_NAVIGATION_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+BOOL
+Navigation_Initialize(void);
+
+void
+Navigation_Uninitialize(void);
+
+BOOL
+Navigation_SelectDevice(const char *name);
+
+BOOL
+Navigation_EditDeviceTitle(const char *name);
+
+BOOL
+Navigation_ProcessMessage(INT msg,
+ INT_PTR param1,
+ INT_PTR param2,
+ INT_PTR param3,
+ INT_PTR *result);
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_NAVIGATION_HEADER
diff --git a/Src/Plugins/Library/ml_devices/navigationIcons.cpp b/Src/Plugins/Library/ml_devices/navigationIcons.cpp
new file mode 100644
index 00000000..038c682d
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/navigationIcons.cpp
@@ -0,0 +1,343 @@
+#include "main.h"
+#include "navigationIcons.h"
+
+#include <vector>
+
+typedef struct IconCacheRecord
+{
+ size_t ref;
+ int index;
+ DeviceImage *image;
+} IconCacheRecord;
+
+typedef std::vector<IconCacheRecord> IconCache;
+
+static IconCache iconCache;
+
+static BOOL
+NavigationIcons_GetSize(unsigned int *width, unsigned int *height)
+{
+ HWND libraryWindow;
+ MLIMAGELISTIMAGESIZE imageSize;
+
+ libraryWindow = Plugin_GetLibraryWindow();
+
+ imageSize.hmlil = MLNavCtrl_GetImageList(libraryWindow);
+ if (NULL == imageSize.hmlil)
+ return FALSE;
+
+ if (FALSE == MLImageList_GetImageSize(libraryWindow, &imageSize))
+ return FALSE;
+
+ *width = imageSize.cx;
+ *height = imageSize.cy;
+
+ return TRUE;
+}
+
+static DeviceImage*
+NavigationIcons_GetDeviceImage(ifc_device *device, unsigned int width, unsigned int height)
+{
+ wchar_t path[MAX_PATH * 2] = {0};
+
+ if (NULL == device)
+ return NULL;
+
+ if (FAILED(device->GetIcon(path, ARRAYSIZE(path), width, height)))
+ return NULL;
+
+ return DeviceImageCache_GetImage(Plugin_GetImageCache(),
+ path, width, height, NULL, NULL);
+}
+
+static DeviceImage*
+NavigationIcons_GetDeviceTypeImage(ifc_device *device, unsigned int width, unsigned int height)
+{
+ ifc_devicetype *type;
+ DeviceImage *image;
+ wchar_t path[MAX_PATH * 2] = {0};
+
+ if (NULL == device ||
+ NULL == WASABI_API_DEVICES ||
+ S_OK != WASABI_API_DEVICES->TypeFind(device->GetName(), &type))
+ {
+ return NULL;
+ }
+
+ if (SUCCEEDED(type->GetIcon(path, ARRAYSIZE(path), width, height)))
+ {
+
+ image = DeviceImageCache_GetImage(Plugin_GetImageCache(),
+ path, width, height, NULL, NULL);
+ }
+ else
+ image = NULL;
+
+ type->Release();
+
+ return image;
+}
+
+static DeviceImage*
+NavigationIcons_GetDefaultImage(ifc_device *device, unsigned int width, unsigned int height)
+{
+ const wchar_t *path;
+
+ path = Plugin_GetDefaultDeviceImage(width, height);
+ if (NULL == path)
+ return NULL;
+
+ return DeviceImageCache_GetImage(Plugin_GetImageCache(), path, width, height, NULL, NULL);
+}
+
+static int
+NavigationIcons_RegisterDeviceIcon(DeviceImage *image, int iconIndex)
+{
+ MLIMAGESOURCE imageSource;
+ MLIMAGELISTITEM listItem;
+ HWND libraryWindow;
+ HBITMAP bitmap;
+
+ if (NULL == image)
+ return -1;
+
+ libraryWindow = Plugin_GetLibraryWindow();
+ if (NULL == libraryWindow)
+ return -1;
+
+
+ bitmap = DeviceImage_GetBitmap(image,
+ DeviceImage_ExactSize | DeviceImage_AlignHCenter | DeviceImage_AlignVCenter);
+
+ if (NULL == bitmap)
+ return -1;
+
+
+ imageSource.cbSize = sizeof(imageSource);
+ imageSource.lpszName = (LPCWSTR)bitmap;
+ imageSource.type = SRC_TYPE_HBITMAP;
+ imageSource.bpp = 32;
+ imageSource.flags = 0;
+ imageSource.hInst = NULL;
+
+ listItem.cbSize = sizeof(listItem);
+ listItem.hmlil = MLNavCtrl_GetImageList(libraryWindow);
+ listItem.filterUID = MLIF_FILTER3_UID;
+ listItem.pmlImgSource = &imageSource;
+ listItem.mlilIndex = iconIndex;
+
+ if (NULL == listItem.hmlil)
+ return -1;
+
+ if (listItem.mlilIndex >= 0)
+ {
+ if (FALSE == MLImageList_Replace(libraryWindow, &listItem))
+ return -1;
+
+ return listItem.mlilIndex;
+ }
+
+ return MLImageList_Add(libraryWindow, &listItem);
+}
+
+
+static IconCacheRecord *
+NavigationIcons_FindCacheRecord(DeviceImage *image)
+{
+ size_t index;
+ IconCacheRecord *record;
+
+ if (NULL == image)
+ return NULL;
+
+ index = iconCache.size();
+ while(index--)
+ {
+ record = &iconCache[index];
+ if (record->image == image)
+ return record;
+ }
+
+ return NULL;
+}
+
+static IconCacheRecord *
+NavigationIcons_FindAvailableCacheRecord()
+{
+ size_t index;
+ IconCacheRecord *record;
+
+ index = iconCache.size();
+ while(index--)
+ {
+ record = &iconCache[index];
+ if (0 == record->ref)
+ return record;
+ }
+
+ return NULL;
+}
+
+static IconCacheRecord *
+NavigationIcons_FindCacheRecordByIndex(int iconIndex)
+{
+ size_t index;
+ IconCacheRecord *record;
+
+ if (iconIndex < 0)
+ return NULL;
+
+ index = iconCache.size();
+ while(index--)
+ {
+ record = &iconCache[index];
+ if (record->index == iconIndex)
+ return record;
+ }
+
+ return NULL;
+}
+
+static IconCacheRecord *
+NavigationIcons_CreateCacheRecord(DeviceImage *image, int iconIndex)
+{
+ IconCacheRecord record;
+
+ if (NULL == image || -1 == iconIndex)
+ return NULL;
+
+ record.ref = 1;
+ record.index = iconIndex;
+ record.image = image;
+
+ DeviceImage_AddRef(image);
+
+ iconCache.push_back(record);
+ return &iconCache.back();
+}
+
+static HBITMAP
+NavigationIcons_GetDeviceImageBitmap(DeviceImage *image)
+{
+ return DeviceImage_GetBitmap(image,
+ DeviceImage_ExactSize |
+ DeviceImage_AlignHCenter |
+ DeviceImage_AlignVCenter);
+}
+
+int
+NavigationIcons_GetDeviceIconIndex(ifc_device *device)
+{
+ DeviceImage *image;
+ unsigned int width, height;
+ IconCacheRecord *record;
+ int iconIndex;
+ size_t attempt;
+
+ if (FALSE == NavigationIcons_GetSize(&width, &height))
+ return -1;
+
+ for(attempt = 0; attempt < 3; attempt++)
+ {
+ switch(attempt)
+ {
+ case 0:
+ image = NavigationIcons_GetDeviceImage(device, width, height);
+ break;
+ case 1:
+ image = NavigationIcons_GetDeviceTypeImage(device, width, height);
+ break;
+ case 2:
+ image = NavigationIcons_GetDefaultImage(device, width, height);
+ break;
+ }
+
+ record = (NULL != image) ?
+ NavigationIcons_FindCacheRecord(image) :
+ NULL;
+
+ if (NULL == record &&
+ NULL == NavigationIcons_GetDeviceImageBitmap(image))
+ {
+ continue;
+ }
+
+ break;
+ }
+
+ if (NULL != record)
+ {
+ record->ref++;
+ iconIndex = record->index;
+ }
+ else
+ {
+ record = NavigationIcons_FindAvailableCacheRecord();
+ if (NULL != record)
+ {
+ iconIndex = NavigationIcons_RegisterDeviceIcon(image, record->index);
+ if (-1 != iconIndex)
+ {
+ record->ref++;
+ record->image = image;
+ }
+ }
+ else
+ {
+ iconIndex = NavigationIcons_RegisterDeviceIcon(image, -1);
+ if (-1 != iconIndex)
+ {
+ IconCacheRecord newRecord;
+ newRecord.ref = 1;
+ newRecord.image = image;
+ newRecord.index = iconIndex;
+ iconCache.push_back(newRecord);
+ }
+ }
+ }
+
+ if (-1 == iconIndex)
+ DeviceImage_Release(image);
+
+ return iconIndex;
+}
+
+BOOL
+NavigationIcons_ReleaseIconIndex(int iconIndex)
+{
+ IconCacheRecord *record;
+
+ record = NavigationIcons_FindCacheRecordByIndex(iconIndex);
+ if (NULL == record)
+ return FALSE;
+
+ if (0 == record->ref)
+ return FALSE;
+
+ record->ref--;
+ DeviceImage_Release(record->image);
+ if (0 == record->ref)
+ record->image = NULL;
+
+ return TRUE;
+}
+
+void
+NavigationIcons_ClearCache()
+{
+ size_t index;
+ IconCacheRecord *record;
+
+ index = iconCache.size();
+ while(index--)
+ {
+ record = &iconCache[index];
+ if (NULL != record->image)
+ {
+ while(record->ref--)
+ DeviceImage_Release(record->image);
+ record->image = NULL;
+ }
+ record->ref = 0;
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/navigationIcons.h b/Src/Plugins/Library/ml_devices/navigationIcons.h
new file mode 100644
index 00000000..1b53fc75
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/navigationIcons.h
@@ -0,0 +1,21 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_NAVIGATION_ICONS_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_NAVIGATION_ICONS_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+int
+NavigationIcons_GetDeviceIconIndex(ifc_device *device);
+
+BOOL
+NavigationIcons_ReleaseIconIndex(int iconIndex);
+
+void
+NavigationIcons_ClearCache();
+
+
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_NAVIGATION_ICONS_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/plugin.cpp b/Src/Plugins/Library/ml_devices/plugin.cpp
new file mode 100644
index 00000000..e8396373
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/plugin.cpp
@@ -0,0 +1,357 @@
+#include "main.h"
+#include <vector>
+
+//#include <crtdbg.h>
+#include <strsafe.h>
+
+typedef std::vector<PluginUnloadCallback> UnloadCallbackList;
+
+static int Plugin_Init();
+static void Plugin_Quit();
+
+static INT_PTR
+Plugin_MessageProc(INT msg, INT_PTR param1, INT_PTR param2, INT_PTR param3);
+
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_devices.dll)",
+ Plugin_Init,
+ Plugin_Quit,
+ Plugin_MessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+static UnloadCallbackList *unloadCallbacks = NULL;
+static DeviceImageCache *imageCache = NULL;
+static HWND eventRelayWindow = NULL;
+
+HINSTANCE
+Plugin_GetInstance(void)
+{
+ return plugin.hDllInstance;
+}
+
+HWND
+Plugin_GetWinampWindow(void)
+{
+ return plugin.hwndWinampParent;
+}
+
+HWND
+Plugin_GetLibraryWindow(void)
+{
+ return plugin.hwndLibraryParent;
+}
+
+static void
+Plugin_SetDescription()
+{
+ static wchar_t szDescription[256];
+ StringCchPrintf(szDescription, ARRAYSIZE(szDescription),
+ WASABI_API_LNGSTRINGW(IDS_PLUGIN_NAME),
+ PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR);
+ plugin.description = (char*)szDescription;
+}
+
+static int Plugin_Init()
+{
+ unloadCallbacks = NULL;
+
+// _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF |
+// _CRTDBG_CHECK_ALWAYS_DF | _CRTDBG_CHECK_CRT_DF);
+
+ if (FALSE == Wasabi_InitializeFromWinamp(plugin.hDllInstance, plugin.hwndWinampParent))
+ return ML_INIT_FAILURE;
+
+ Wasabi_LoadDefaultServices();
+
+ Plugin_SetDescription();
+
+ imageCache = DeviceImageCache_Create();
+
+ if (NULL == eventRelayWindow)
+ {
+ eventRelayWindow = EventRelay_CreateWindow();
+ if (NULL == eventRelayWindow)
+ return 2;
+ }
+
+ if (FALSE == Navigation_Initialize())
+ {
+ if (NULL != eventRelayWindow)
+ {
+ DestroyWindow(eventRelayWindow);
+ eventRelayWindow = NULL;
+ }
+
+ Wasabi_Release();
+ return 3;
+ }
+
+ DeviceCommands_Register();
+
+ return ML_INIT_SUCCESS;
+}
+
+static void Plugin_Quit()
+{
+ if (NULL != unloadCallbacks)
+ {
+ size_t index = unloadCallbacks->size();
+ while(index--)
+ unloadCallbacks->at(index)();
+ delete(unloadCallbacks);
+ }
+
+ if (NULL != eventRelayWindow)
+ {
+ DestroyWindow(eventRelayWindow);
+ eventRelayWindow = NULL;
+ }
+
+ Navigation_Uninitialize();
+
+ if (NULL != imageCache)
+ {
+ DeviceImageCache_Free(imageCache);
+ imageCache = NULL;
+ }
+
+ Wasabi_Release();
+}
+
+static INT_PTR
+Plugin_MessageProc(INT msg, INT_PTR param1, INT_PTR param2, INT_PTR param3)
+{
+ INT_PTR result = 0;
+
+ if (FALSE != Navigation_ProcessMessage(msg, param1, param2, param3, &result))
+ return result;
+
+ return FALSE;
+}
+
+BOOL
+Plugin_RegisterUnloadCallback(PluginUnloadCallback callback)
+{
+ if (NULL == callback)
+ return FALSE;
+
+ if (NULL == unloadCallbacks)
+ {
+ unloadCallbacks = new UnloadCallbackList();
+ if (NULL == unloadCallbacks)
+ return FALSE;
+ }
+
+ unloadCallbacks->push_back(callback);
+ return TRUE;
+}
+
+DeviceImageCache *
+Plugin_GetImageCache()
+{
+ return imageCache;
+}
+
+HWND
+Plugin_GetEventRelayWindow()
+{
+ return eventRelayWindow;
+}
+
+const wchar_t *
+Plugin_GetDefaultDeviceImage(unsigned int width, unsigned int height)
+{
+ const ImageInfo *image;
+ const ImageInfo deviceImages[] =
+ {
+ {16, 16, MAKEINTRESOURCE(IDR_GENERIC_DEVICE_16x16_IMAGE)},
+ {160, 160, MAKEINTRESOURCE(IDR_GENERIC_DEVICE_160x160_IMAGE)},
+ };
+
+ image = Image_GetBestFit(deviceImages, ARRAYSIZE(deviceImages), width, height);
+ if (NULL == image)
+ return NULL;
+
+ return image->path;
+}
+
+HRESULT
+Plugin_EnsurePathExist(const wchar_t *path)
+{
+ unsigned long errorCode;
+ unsigned int errorMode;
+
+ errorCode = ERROR_SUCCESS;
+ errorMode = SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
+
+ if (0 == CreateDirectory(path, NULL))
+ {
+ errorCode = GetLastError();
+ if (ERROR_PATH_NOT_FOUND == errorCode)
+ {
+ const wchar_t *block, *cursor;
+ wchar_t buffer[MAX_PATH] = {0};
+
+ block = path;
+ cursor = PathFindNextComponent(block);
+
+ errorCode = (cursor == block ||
+ S_OK != StringCchCopyN(buffer, ARRAYSIZE(buffer), block, (cursor - block))) ?
+ ERROR_INVALID_NAME : ERROR_SUCCESS;
+
+ block = cursor;
+
+ while(ERROR_SUCCESS == errorCode &&
+ NULL != (cursor = PathFindNextComponent(block)))
+ {
+ if (cursor == block ||
+ S_OK != StringCchCatN(buffer, ARRAYSIZE(buffer), block, (cursor - block)))
+ {
+ errorCode = ERROR_INVALID_NAME;
+ }
+
+ if (ERROR_SUCCESS == errorCode &&
+ FALSE == CreateDirectory(buffer, NULL))
+ {
+ errorCode = GetLastError();
+ if (ERROR_ALREADY_EXISTS == errorCode)
+ errorCode = ERROR_SUCCESS;
+ }
+ block = cursor;
+ }
+ }
+
+ if (ERROR_ALREADY_EXISTS == errorCode)
+ errorCode = ERROR_SUCCESS;
+ }
+
+ SetErrorMode(errorMode);
+ SetLastError(errorCode);
+ return HRESULT_FROM_WIN32(errorCode);
+}
+
+BOOL
+Plugin_GetResourceString(const wchar_t *resourceName, const wchar_t *resourceType, wchar_t *buffer, size_t bufferMax)
+{
+ unsigned long filenameLength;
+
+ if (NULL == resourceName)
+ return FALSE;
+
+ if (FAILED(StringCchCopyEx(buffer, bufferMax, L"res://", &buffer, &bufferMax, 0)))
+ return FALSE;
+
+ filenameLength = GetModuleFileName(Plugin_GetInstance(), buffer, (DWORD)bufferMax);
+ if (0 == filenameLength || bufferMax == filenameLength)
+ return FALSE;
+
+ buffer += filenameLength;
+ bufferMax -= filenameLength;
+
+ if (NULL != resourceType)
+ {
+ if (FALSE != IS_INTRESOURCE(resourceType))
+ {
+ if (FAILED(StringCchPrintfEx(buffer, bufferMax, &buffer, &bufferMax, 0, L"/#%d", (int)(INT_PTR)resourceType)))
+ return FALSE;
+ }
+ else
+ {
+ if (FAILED(StringCchPrintfEx(buffer, bufferMax, &buffer, &bufferMax, 0, L"/%s", resourceType)))
+ return FALSE;
+ }
+ }
+
+ if (FALSE != IS_INTRESOURCE(resourceName))
+ {
+ if (FAILED(StringCchPrintfEx(buffer, bufferMax, &buffer, &bufferMax, 0, L"/#%d", (int)(INT_PTR)resourceName)))
+ return FALSE;
+ }
+ else
+ {
+ if (FAILED(StringCchPrintfEx(buffer, bufferMax, &buffer, &bufferMax, 0, L"/%s", resourceName)))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+HMENU
+Plugin_LoadMenu()
+{
+ return WASABI_API_LOADMENUW(IDR_PLUGIN_MENU);
+}
+
+BOOL
+Plugin_ShowHelp()
+{
+ BOOL result;
+ wchar_t buffer[8192] = {0};
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_PLUGIN_HELP_URL, buffer, ARRAYSIZE(buffer));
+ if (L'\0' == buffer[0])
+ {
+ if (FAILED(StringCchCopy(buffer, ARRAYSIZE(buffer),
+ L"https://help.winamp.com/hc/articles/8106455294612-Winamp-Portables-Guide")))
+ {
+ return FALSE;
+ }
+ }
+
+ result = MediaLibrary_ShowHelp(plugin.hwndLibraryParent, buffer);
+
+ return result;
+}
+
+BOOL
+Plugin_BeginDiscovery()
+{
+ if (NULL == WASABI_API_DEVICES ||
+ FAILED(WASABI_API_DEVICES->BeginDiscovery()))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL
+Plugin_OpenUrl(HWND ownerWindow, const wchar_t *url, BOOL forceExternal)
+{
+ BOOL result;
+ HCURSOR cursor;
+
+ if (NULL == WASABI_API_WINAMP)
+ return FALSE;
+
+ cursor = LoadCursor(NULL, IDC_APPSTARTING);
+ if (NULL != cursor)
+ cursor = SetCursor(cursor);
+
+ if (FALSE != forceExternal)
+ {
+ HINSTANCE instance = ShellExecute(ownerWindow, L"open", url, NULL, NULL, SW_SHOWNORMAL);
+ result = ((INT_PTR)instance > 32) ? TRUE: FALSE;
+ }
+ else
+ {
+ HRESULT hr = WASABI_API_WINAMP->OpenUrl(ownerWindow, url);
+ result = SUCCEEDED(hr);
+ }
+
+ if (NULL != cursor)
+ SetCursor(cursor);
+
+ return result;
+}
+
+EXTERN_C __declspec(dllexport) winampMediaLibraryPlugin *
+winampGetMediaLibraryPlugin()
+{
+ return &plugin;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/plugin.h b/Src/Plugins/Library/ml_devices/plugin.h
new file mode 100644
index 00000000..b09f5499
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/plugin.h
@@ -0,0 +1,33 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_PLUGIN_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_PLUGIN_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../Plugins/General/gen_ml/ml.h"
+#include "./imageCache.h"
+#include "./deviceManagerHandler.h"
+#include "./deviceHandler.h"
+
+#define PLUGIN_VERSION_MAJOR 1
+#define PLUGIN_VERSION_MINOR 35
+
+typedef void (CALLBACK *PluginUnloadCallback)(void);
+
+HINSTANCE Plugin_GetInstance(void);
+HWND Plugin_GetWinampWindow(void);
+HWND Plugin_GetLibraryWindow(void);
+BOOL Plugin_RegisterUnloadCallback(PluginUnloadCallback callback);
+DeviceImageCache *Plugin_GetImageCache();
+HWND Plugin_GetEventRelayWindow();
+const wchar_t *Plugin_GetDefaultDeviceImage(unsigned int width, unsigned int height);
+HRESULT Plugin_EnsurePathExist(const wchar_t *path);
+BOOL Plugin_GetResourceString(const wchar_t *resourceName, const wchar_t *resourceType, wchar_t *buffer, size_t bufferMax);
+HMENU Plugin_LoadMenu();
+BOOL Plugin_ShowHelp();
+BOOL Plugin_BeginDiscovery();
+BOOL Plugin_OpenUrl(HWND ownerWindow, const wchar_t *url, BOOL forceExternal);
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_PLUGIN_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/png.rc b/Src/Plugins/Library/ml_devices/png.rc
new file mode 100644
index 00000000..38fdea1e
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/png.rc
@@ -0,0 +1,50 @@
+#include "resource.h"
+/////////////////////////////////////////////////////////////////////////////
+//
+// Data
+//
+IDR_WELCOME_WIDGET_IMAGE RCDATA
+".\\resources\\devices.png"
+IDR_SPACEBAR_IMAGE RCDATA
+".\\resources\\spacebar.png"
+IDR_CATEGORY_ARROWS_IMAGE RCDATA
+".\\resources\\arrows.png"
+IDR_ITEM_HOVER_IMAGE RCDATA
+".\\resources\\item-hover.png"
+IDR_ITEM_SELECT_IMAGE RCDATA
+".\\resources\\item-select.png"
+IDR_COMMAND_BACKGROUND_IMAGE RCDATA
+".\\resources\\command-bg.png"
+IDR_COMMAND_SECONDARY_BACKGROUND_IMAGE RCDATA
+".\\resources\\command-secondary-bg.png"
+IDR_PROGRESS_LARGE_IMAGE RCDATA
+".\\resources\\progress-large.png"
+IDR_PROGRESS_SMALL_IMAGE RCDATA
+".\\resources\\progress-small.png"
+IDR_ACTION_BACKGROUND_IMAGE RCDATA
+".\\resources\\action-bg.png"
+IDR_GENERIC_DEVICE_160x160_IMAGE RCDATA
+".\\resources\\generic-device-160x160.png"
+IDR_GENERIC_DEVICE_16x16_IMAGE RCDATA
+".\\resources\\generic-device-16x16.png"
+IDR_UNKNOWN_COMMAND_LARGE_IMAGE RCDATA
+".\\resources\\unknown-command-large.png"
+IDR_SYNC_COMMAND_LARGE_IMAGE RCDATA
+".\\resources\\sync-command-large.png"
+IDR_SYNC_COMMAND_SMALL_IMAGE RCDATA
+".\\resources\\sync-command-small.png"
+IDR_CANCEL_SYNC_COMMAND_SMALL_IMAGE RCDATA
+".\\resources\\cancel-sync-command-small.png"
+IDR_ATTACH_COMMAND_LARGE_IMAGE RCDATA
+".\\resources\\attach-command-large.png"
+IDR_ATTACH_COMMAND_SMALL_IMAGE RCDATA
+".\\resources\\attach-command-small.png"
+IDR_DETACH_COMMAND_LARGE_IMAGE RCDATA
+".\\resources\\detach-command-large.png"
+IDR_DETACH_COMMAND_SMALL_IMAGE RCDATA
+".\\resources\\detach-command-small.png"
+IDR_EJECT_COMMAND_SMALL_IMAGE RCDATA
+".\\resources\\eject-command-small.png"
+IDR_WELCOME_WIDGET_TITLE_IMAGE RCDATA
+".\\resources\\devices-title-en.png"
+
diff --git a/Src/Plugins/Library/ml_devices/resource.h b/Src/Plugins/Library/ml_devices/resource.h
new file mode 100644
index 00000000..8d75063f
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resource.h
@@ -0,0 +1,105 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_devices.rc
+//
+#define IDR_CANCEL_SYNC_COMMAND_LARGE_IMAGE 0
+#define IDR_EJECT_COMMAND_LARGE_IMAGE 0
+#define IDR_RENAME_COMMAND_LARGE_IMAGE 0
+#define IDR_RENAME_COMMAND_SMALL_IMAGE 0
+#define IDR_VIEW_OPEN_COMMAND_LARGE_IMAGE 0
+#define IDR_VIEW_OPEN_COMMAND_SMALL_IMAGE 0
+#define IDR_PREFERENCES_COMMAND_LARGE_IMAGE 0
+#define IDR_PREFERENCES_COMMAND_SMALL_IMAGE 0
+#define IDR_PLAYLIST_CREATE_COMMAND_LARGE_IMAGE 0
+#define IDR_PLAYLIST_CREATE_COMMAND_SMALL_IMAGE 0
+#define IDD_MANAGER_VIEW 102
+#define IDS_WELCOMEWIDGET_TITLE 102
+#define IDS_WELCOMEWIDGET_TEXT 103
+#define IDR_PLUGIN_MENU 103
+#define IDS_DEVICES_NAVIGATION_NODE 104
+#define IDS_CATEGORY_ATTACHED 105
+#define IDS_CATEGORY_DISCOVERED 106
+#define IDS_INFOWIDGET_TITLE 107
+#define IDS_DEVICE_SERVICE_NOT_FOUND 108
+#define IDS_CREATE_DEVICE_VIEW_FAILED 109
+#define IDS_SYNC_COMMAND_TITLE 110
+#define IDS_SYNC_COMMAND_DESC 111
+#define IDS_CANCEL_SYNC_COMMAND_TITLE 112
+#define IDS_CANCEL_SYNC_COMMAND_DESC 113
+#define IDS_CREATE_MANAGER_VIEW_FAILED 114
+#define IDS_ATTACH_COMMAND_TITLE 115
+#define IDS_ATTACH_COMMAND_DESC 116
+#define IDS_DETACH_COMMAND_TITLE 117
+#define IDS_DETACH_COMMAND_DESC 118
+#define IDS_EJECT_COMMAND_TITLE 119
+#define IDS_EJECT_COMMAND_DESC 120
+#define IDS_DEVICE_TYPE_SHORT 121
+#define IDS_DEVICE_CONNECTION_SHORT 122
+#define IDS_TOTAL_SPACE 123
+#define IDS_FREE_SPACE 124
+#define IDS_USED_SPACE 125
+#define IDS_FRACTION_SEPARATOR 126
+#define IDS_DEVICE_ACTIVITY_SHORT 132
+#define IDS_PLUGIN_HELP_URL 133
+#define IDS_RENAME_COMMAND_TITLE 134
+#define IDS_RENAME_COMMAND_DESC 135
+#define IDS_VIEW_OPEN_COMMAND_TITLE 136
+#define IDS_VIEW_OPEN_COMMAND_DESC 137
+#define IDS_PREFERENCES_COMMAND_TITLE 138
+#define IDS_PREFERENCES_COMMAND_DESC 139
+#define IDS_PLAYLIST_CREATE_COMMAND_TITLE 140
+#define IDS_PLAYLIST_CREATE_COMMAND_DESC 141
+#define IDS_MESSAGEBOX_TITLE 142
+#define IDS_MESSAGE_UNABLE_TO_RENAME 143
+#define IDS_DEVICE_MODEL_SHORT 144
+#define IDS_DEVICE_STATUS_SHORT 145
+#define IDS_STATUS_DISCOVERY_ACTIVE 146
+#define IDS_STATUS_SPACE_TEMPLATE 147
+#define IDS_CATEGORY_ATTACHED_EMPTY_TEXT 148
+#define IDS_WELCOMEWIDGET_HELP_URL 149
+#define IDS_HELP_LINK 150
+#define IDC_BUTTON_DISCOVER 1001
+#define IDC_SLIDER_ZOOM 1002
+#define IDR_WELCOME_WIDGET_TITLE_IMAGE 19999
+#define IDR_WELCOME_WIDGET_IMAGE 20000
+#define IDR_SPACEBAR_IMAGE 20001
+#define IDR_CATEGORY_ARROWS_IMAGE 20002
+#define IDR_ITEM_HOVER_IMAGE 20003
+#define IDR_ITEM_SELECT_IMAGE 20004
+#define IDR_COMMAND_BACKGROUND_IMAGE 20005
+#define IDR_COMMAND_SECONDARY_BACKGROUND_IMAGE 20006
+#define IDR_ACTION_BACKGROUND_IMAGE 20007
+#define IDR_PROGRESS_LARGE_IMAGE 20008
+#define IDR_PROGRESS_SMALL_IMAGE 20009
+#define IDR_GENERIC_DEVICE_160x160_IMAGE 20010
+#define IDR_GENERIC_DEVICE_16x16_IMAGE 20011
+#define IDR_UNKNOWN_COMMAND_LARGE_IMAGE 20012
+#define IDR_UNKNOWN_COMMAND_SMALL_IMAGE 20013
+#define IDR_SYNC_COMMAND_LARGE_IMAGE 20014
+#define IDR_SYNC_COMMAND_SMALL_IMAGE 20015
+#define IDR_CANCEL_SYNC_COMMAND_SMALL_IMAGE 20017
+#define IDR_ATTACH_COMMAND_LARGE_IMAGE 20018
+#define IDR_ATTACH_COMMAND_SMALL_IMAGE 20019
+#define IDR_DETACH_COMMAND_LARGE_IMAGE 20020
+#define IDR_DETACH_COMMAND_SMALL_IMAGE 20021
+#define IDR_EJECT_COMMAND_SMALL_IMAGE 20023
+#define ID_NAVIGATION_OPEN_VIEW 40005
+#define ID_DISCOVERY_BEGIN 40006
+#define ID_SHOW_PREFERENCES 40007
+#define ID_SHOW_HELP 40008
+#define ID_OPEN_VIEW 40009
+#define ID_VIEW_OPEN 40010
+#define ID_PLUGIN_PREFERENCES 40012
+#define ID_PLUGIN_HELP 40013
+#define IDS_PLUGIN_NAME 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 104
+#define _APS_NEXT_COMMAND_VALUE 40014
+#define _APS_NEXT_CONTROL_VALUE 1003
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_devices/resources/action-bg.png b/Src/Plugins/Library/ml_devices/resources/action-bg.png
new file mode 100644
index 00000000..3831d9dc
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/action-bg.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/arrows.png b/Src/Plugins/Library/ml_devices/resources/arrows.png
new file mode 100644
index 00000000..c5229d40
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/arrows.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/attach-command-large.png b/Src/Plugins/Library/ml_devices/resources/attach-command-large.png
new file mode 100644
index 00000000..bb645908
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/attach-command-large.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/attach-command-small.png b/Src/Plugins/Library/ml_devices/resources/attach-command-small.png
new file mode 100644
index 00000000..64f2e081
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/attach-command-small.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/cancel-sync-command-small.png b/Src/Plugins/Library/ml_devices/resources/cancel-sync-command-small.png
new file mode 100644
index 00000000..a467a588
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/cancel-sync-command-small.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/command-bg.png b/Src/Plugins/Library/ml_devices/resources/command-bg.png
new file mode 100644
index 00000000..ac50f5b0
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/command-bg.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/command-secondary-bg.png b/Src/Plugins/Library/ml_devices/resources/command-secondary-bg.png
new file mode 100644
index 00000000..3831d9dc
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/command-secondary-bg.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/commands/detach.png b/Src/Plugins/Library/ml_devices/resources/commands/detach.png
new file mode 100644
index 00000000..074cebfd
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/commands/detach.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/commands/eject.png b/Src/Plugins/Library/ml_devices/resources/commands/eject.png
new file mode 100644
index 00000000..afa2e21d
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/commands/eject.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/commands/settings.png b/Src/Plugins/Library/ml_devices/resources/commands/settings.png
new file mode 100644
index 00000000..0e3ebacb
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/commands/settings.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/commands/sync.png b/Src/Plugins/Library/ml_devices/resources/commands/sync.png
new file mode 100644
index 00000000..86cb780a
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/commands/sync.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/connections/bluetooth.png b/Src/Plugins/Library/ml_devices/resources/connections/bluetooth.png
new file mode 100644
index 00000000..57d7062b
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/connections/bluetooth.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/connections/cloud.png b/Src/Plugins/Library/ml_devices/resources/connections/cloud.png
new file mode 100644
index 00000000..57c95556
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/connections/cloud.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/connections/usb.png b/Src/Plugins/Library/ml_devices/resources/connections/usb.png
new file mode 100644
index 00000000..961dbcee
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/connections/usb.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/connections/wifi.png b/Src/Plugins/Library/ml_devices/resources/connections/wifi.png
new file mode 100644
index 00000000..b0de726a
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/connections/wifi.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/detach-command-large.png b/Src/Plugins/Library/ml_devices/resources/detach-command-large.png
new file mode 100644
index 00000000..cf38b271
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/detach-command-large.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/detach-command-small.png b/Src/Plugins/Library/ml_devices/resources/detach-command-small.png
new file mode 100644
index 00000000..b34a7515
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/detach-command-small.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/devices-title-en.png b/Src/Plugins/Library/ml_devices/resources/devices-title-en.png
new file mode 100644
index 00000000..fd6bf09b
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/devices-title-en.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/devices.png b/Src/Plugins/Library/ml_devices/resources/devices.png
new file mode 100644
index 00000000..c2f3079f
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/devices.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/eject-command-small.png b/Src/Plugins/Library/ml_devices/resources/eject-command-small.png
new file mode 100644
index 00000000..503aebbe
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/eject-command-small.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/food/apple.png b/Src/Plugins/Library/ml_devices/resources/food/apple.png
new file mode 100644
index 00000000..c4c75712
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/food/apple.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/food/banana.png b/Src/Plugins/Library/ml_devices/resources/food/banana.png
new file mode 100644
index 00000000..7bb201fd
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/food/banana.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/food/bread.png b/Src/Plugins/Library/ml_devices/resources/food/bread.png
new file mode 100644
index 00000000..f41e0bd7
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/food/bread.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/food/broccoli.png b/Src/Plugins/Library/ml_devices/resources/food/broccoli.png
new file mode 100644
index 00000000..0e27da6f
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/food/broccoli.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/food/carrot.png b/Src/Plugins/Library/ml_devices/resources/food/carrot.png
new file mode 100644
index 00000000..b435271d
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/food/carrot.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/food/cereal.png b/Src/Plugins/Library/ml_devices/resources/food/cereal.png
new file mode 100644
index 00000000..2cf401f5
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/food/cereal.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/food/cucumber.png b/Src/Plugins/Library/ml_devices/resources/food/cucumber.png
new file mode 100644
index 00000000..38878a34
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/food/cucumber.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/food/eggplant.png b/Src/Plugins/Library/ml_devices/resources/food/eggplant.png
new file mode 100644
index 00000000..a64fb0df
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/food/eggplant.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/food/grape.png b/Src/Plugins/Library/ml_devices/resources/food/grape.png
new file mode 100644
index 00000000..03094de9
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/food/grape.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/food/lemon.png b/Src/Plugins/Library/ml_devices/resources/food/lemon.png
new file mode 100644
index 00000000..ffabe0ba
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/food/lemon.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/food/muffin.png b/Src/Plugins/Library/ml_devices/resources/food/muffin.png
new file mode 100644
index 00000000..f47db34d
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/food/muffin.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/food/orange.png b/Src/Plugins/Library/ml_devices/resources/food/orange.png
new file mode 100644
index 00000000..431f1cb3
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/food/orange.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/food/pasta.png b/Src/Plugins/Library/ml_devices/resources/food/pasta.png
new file mode 100644
index 00000000..6edf4326
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/food/pasta.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/food/peach.png b/Src/Plugins/Library/ml_devices/resources/food/peach.png
new file mode 100644
index 00000000..232fe5a4
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/food/peach.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/food/pear.png b/Src/Plugins/Library/ml_devices/resources/food/pear.png
new file mode 100644
index 00000000..f25c4823
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/food/pear.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/food/pepper.png b/Src/Plugins/Library/ml_devices/resources/food/pepper.png
new file mode 100644
index 00000000..1cf1ac68
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/food/pepper.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/food/pretzel.png b/Src/Plugins/Library/ml_devices/resources/food/pretzel.png
new file mode 100644
index 00000000..f9ebb77d
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/food/pretzel.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/food/rice.png b/Src/Plugins/Library/ml_devices/resources/food/rice.png
new file mode 100644
index 00000000..f5bc2993
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/food/rice.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/generic-device-160x160.png b/Src/Plugins/Library/ml_devices/resources/generic-device-160x160.png
new file mode 100644
index 00000000..2c934863
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/generic-device-160x160.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/generic-device-16x16.png b/Src/Plugins/Library/ml_devices/resources/generic-device-16x16.png
new file mode 100644
index 00000000..537d4ef2
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/generic-device-16x16.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/item-hover.png b/Src/Plugins/Library/ml_devices/resources/item-hover.png
new file mode 100644
index 00000000..20190ae8
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/item-hover.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/item-select.png b/Src/Plugins/Library/ml_devices/resources/item-select.png
new file mode 100644
index 00000000..9bee9f57
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/item-select.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/progress-large.png b/Src/Plugins/Library/ml_devices/resources/progress-large.png
new file mode 100644
index 00000000..5eecb405
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/progress-large.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/progress-small.png b/Src/Plugins/Library/ml_devices/resources/progress-small.png
new file mode 100644
index 00000000..63eadd23
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/progress-small.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/spacebar.png b/Src/Plugins/Library/ml_devices/resources/spacebar.png
new file mode 100644
index 00000000..29114b2b
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/spacebar.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/sync-command-large.png b/Src/Plugins/Library/ml_devices/resources/sync-command-large.png
new file mode 100644
index 00000000..86cb780a
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/sync-command-large.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/sync-command-small.png b/Src/Plugins/Library/ml_devices/resources/sync-command-small.png
new file mode 100644
index 00000000..3eff1403
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/sync-command-small.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/unknown-command-large.png b/Src/Plugins/Library/ml_devices/resources/unknown-command-large.png
new file mode 100644
index 00000000..24e53214
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/unknown-command-large.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/zoom/192x256.png b/Src/Plugins/Library/ml_devices/resources/zoom/192x256.png
new file mode 100644
index 00000000..890461ca
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/zoom/192x256.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/zoom/24x32.png b/Src/Plugins/Library/ml_devices/resources/zoom/24x32.png
new file mode 100644
index 00000000..b93b7ce9
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/zoom/24x32.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/zoom/48x64.png b/Src/Plugins/Library/ml_devices/resources/zoom/48x64.png
new file mode 100644
index 00000000..c6d159f5
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/zoom/48x64.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/resources/zoom/96x128.png b/Src/Plugins/Library/ml_devices/resources/zoom/96x128.png
new file mode 100644
index 00000000..a7838541
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/resources/zoom/96x128.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_devices/statusBar.cpp b/Src/Plugins/Library/ml_devices/statusBar.cpp
new file mode 100644
index 00000000..5f1d3fe2
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/statusBar.cpp
@@ -0,0 +1,1001 @@
+#include "main.h"
+#include "./statusBar.h"
+#include <vector>
+
+#include <strsafe.h>
+
+typedef struct StatusTextBlock
+{
+ wchar_t *text;
+ size_t length;
+ long width;
+} StatusTextBlock;
+
+typedef struct StaturRecord
+{
+ unsigned int id;
+ StatusTextBlock mainBlock;
+ StatusTextBlock rightBlock;
+} StatusRecord;
+typedef std::vector<StatusRecord*> StatusRecordList;
+
+typedef struct StatusBar
+{
+ HFONT font;
+ HBRUSH backBrush;
+ COLORREF textColor;
+ COLORREF backColor;
+ StatusRecordList list;
+ int idealHeight;
+ long spacing;
+ long leftBlockMinWidth;
+ BackBuffer backBuffer;
+} StatusBar;
+
+#define STATUSBAR(_hwnd) ((StatusBar*)(LONGX86)GetWindowLongPtrW((_hwnd), 0))
+#define STATUSBAR_RET_VOID(_self, _hwnd) {(_self) = STATUSBAR((_hwnd)); if (NULL == (_self)) return;}
+#define STATUSBAR_RET_VAL(_self, _hwnd, _error) {(_self) = STATUSBAR((_hwnd)); if (NULL == (_self)) return (_error);}
+
+static LRESULT CALLBACK
+StatusBar_WindowProc(HWND hwnd, unsigned int uMsg, WPARAM wParam, LPARAM lParam);
+
+
+static ATOM
+StatusBar_GetClassAtom(HINSTANCE instance)
+{
+ WNDCLASSEXW klass;
+ ATOM klassAtom;
+
+ klassAtom = (ATOM)GetClassInfoExW(instance, STATUSBAR_WINDOW_CLASS, &klass);
+ if (0 != klassAtom)
+ return klassAtom;
+
+ memset(&klass, 0, sizeof(klass));
+ klass.cbSize = sizeof(klass);
+ klass.style = 0;
+ klass.lpfnWndProc = StatusBar_WindowProc;
+ klass.cbClsExtra = 0;
+ klass.cbWndExtra = sizeof(StatusBar*);
+ klass.hInstance = instance;
+ klass.hIcon = NULL;
+ klass.hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
+ klass.hbrBackground = NULL;
+ klass.lpszMenuName = NULL;
+ klass.lpszClassName = STATUSBAR_WINDOW_CLASS;
+ klass.hIconSm = NULL;
+ klassAtom = RegisterClassExW(&klass);
+
+ return klassAtom;
+}
+
+HWND
+StatusBar_CreateWindow(unsigned long windowExStyle, const wchar_t *text, unsigned long windowStyle,
+ int x, int y, int width, int height,
+ HWND parentWindow, unsigned int controlId)
+{
+ HINSTANCE instance = GetModuleHandleW(NULL);
+ ATOM klassAtom = StatusBar_GetClassAtom(instance);
+ if (0 == klassAtom)
+ return NULL;
+
+ return CreateWindowExW(WS_EX_NOPARENTNOTIFY | windowExStyle, (LPCWSTR)MAKEINTATOM(klassAtom), text,
+ WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | windowStyle,
+ x, y, width, height, parentWindow, (HMENU)controlId, instance, NULL);
+}
+
+static void
+StatusBar_InitTextBlock(StatusTextBlock *block)
+{
+ if (NULL != block)
+ {
+ block->text = NULL;
+ block->length = ((size_t)-1);
+ block->width = -1;
+ }
+}
+
+static void
+StatusBar_FreeTextBlock(StatusTextBlock *block)
+{
+ if (NULL != block)
+ {
+ ResourceString_Free(block->text);
+ StatusBar_InitTextBlock(block);
+ }
+}
+
+static const wchar_t *
+StatusBar_RetriveText(StatusTextBlock *block)
+{
+ const wchar_t *source;
+ if (NULL == block)
+ return NULL;
+
+ source = block->text;
+ if (FALSE != IS_INTRESOURCE(source) &&
+ NULL != source)
+ {
+ wchar_t buffer[256] = {0};
+ if (NULL == WASABI_API_LNG)
+ return NULL;
+
+ WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)source, buffer, ARRAYSIZE(buffer));
+ block->text = String_Duplicate(buffer);
+ }
+ return block->text;
+}
+
+static size_t
+StatusBar_GetTextBlockLength(StatusTextBlock *block)
+{
+ const wchar_t *text;
+
+ if (NULL == block)
+ return NULL;
+
+ if (((size_t)-1) != block->length)
+ {
+ text = StatusBar_RetriveText(block);
+ block->length = (NULL != text) ? lstrlenW(text) : 0;
+ }
+
+ return block->length;
+}
+
+static const wchar_t *
+StatusBar_GetTextBlockInfo(StatusTextBlock *block, HDC hdc)
+{
+ const wchar_t *text;
+
+ text = StatusBar_RetriveText(block);
+
+ if (((size_t)-1) == block->length)
+ {
+
+ if (FALSE == IS_STRING_EMPTY(text))
+ block->length = lstrlenW(text);
+ else
+ block->length = 0;
+ }
+
+ if (-1 == block->width)
+ {
+ block->width = 0;
+
+ if (0 != block->length)
+ {
+ RECT rect;
+ DRAWTEXTPARAMS textParams;
+
+ textParams.cbSize = sizeof(textParams);
+ textParams.iTabLength = 4;
+ textParams.iLeftMargin = 0;
+ textParams.iRightMargin = 0;
+
+ SetRect(&rect, 0, 0, 0x7FFFFFFF, 0);
+
+ if (FALSE != DrawTextEx(hdc, (wchar_t*)text, -1, &rect,
+ DT_LEFT | DT_TOP | DT_TABSTOP | DT_SINGLELINE |
+ DT_NOPREFIX | DT_EXPANDTABS | DT_NOCLIP | DT_CALCRECT | DT_EDITCONTROL,
+ &textParams))
+ {
+ block->width = RECTWIDTH(rect);
+ }
+ }
+
+ TEXTMETRIC metrics;
+ if (FALSE != GetTextMetrics(hdc, &metrics))
+ {
+ block->width += metrics.tmAveCharWidth/2;
+ }
+
+ }
+
+ return text;
+}
+
+static unsigned int
+StatusBar_GetNextFreeId(StatusBar *self)
+{
+ unsigned int id;
+ size_t index, count;
+ StatusRecord *record;
+ BOOL checkId;
+
+ count = self->list.size();
+ id = (unsigned int)count;
+ do
+ {
+ if (0xFFFFFFFF == id)
+ return STATUS_ERROR;
+
+ checkId = FALSE;
+ index = count;
+ while(index--)
+ {
+ record = self->list[index];
+ if (record->id == id)
+ {
+ id++;
+ checkId = TRUE;
+ break;
+ }
+ }
+
+ } while(FALSE != checkId);
+
+ return id;
+}
+
+static StatusRecord *
+StatusBar_AddRecord(StatusBar *self, const wchar_t *text)
+{
+ StatusRecord *record;
+ unsigned int id;
+
+ id = StatusBar_GetNextFreeId(self);
+ if (STATUS_ERROR == id)
+ return NULL;
+
+ record = (StatusRecord*)malloc(sizeof(StatusRecord));
+ if(NULL == record)
+ return NULL;
+
+ record->id = 0;
+ StatusBar_InitTextBlock(&record->mainBlock);
+ StatusBar_InitTextBlock(&record->rightBlock);
+
+ record->mainBlock.text = ResourceString_Duplicate(text);
+
+ self->list.push_back(record);
+
+ return record;
+}
+
+static StatusRecord *
+StatusBar_FindRecord(StatusBar *self, unsigned int id, size_t *indexOut)
+{
+ StatusRecord *record;
+ size_t index;
+
+ index = self->list.size();
+ while(index--)
+ {
+ record = self->list[index];
+ if (record->id == id)
+ {
+ if (NULL != indexOut)
+ *indexOut = index;
+ return record;
+ }
+ }
+
+ return NULL;
+}
+
+static StatusRecord *
+StatusBar_FindVisibleRecord(StatusBar *self, size_t *indexOut)
+{
+ StatusRecord *record;
+ size_t index;
+ const wchar_t *text;
+
+ index = self->list.size();
+ while(index--)
+ {
+ record = self->list[index];
+
+ text = StatusBar_RetriveText(&record->mainBlock);
+ if (FALSE != IS_STRING_EMPTY(text))
+ text = StatusBar_RetriveText(&record->rightBlock);
+
+ if (FALSE == IS_STRING_EMPTY(text))
+ {
+ if (NULL != indexOut)
+ *indexOut = index;
+ return record;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+StatusBar_Paint(HWND hwnd, HDC hdc, const RECT *paintRect, BOOL erase)
+{
+ StatusBar *self;
+ StatusRecord *record;
+ const wchar_t *text;
+
+ STATUSBAR_RET_VOID(self, hwnd);
+
+ if (FALSE != erase)
+ {
+ FillRect(hdc, paintRect, self->backBrush);
+ }
+
+ record = StatusBar_FindVisibleRecord(self, NULL);
+ if (NULL != record)
+ {
+ COLORREF prevBackColor, prevTextColor;
+ HFONT prevFont;
+ RECT clientRect, textRect;
+ DRAWTEXTPARAMS textParams;
+ long limit;
+
+ prevBackColor = SetBkColor(hdc, self->backColor);
+ prevTextColor = SetTextColor(hdc, self->textColor);
+ prevFont = SelectFont(hdc, self->font);
+
+ textParams.cbSize = sizeof(textParams);
+ textParams.iTabLength = 4;
+ textParams.iLeftMargin = 0;
+ textParams.iRightMargin = 0;
+
+ GetClientRect(hwnd, &clientRect);
+
+ if (-1 == self->spacing)
+ self->spacing = Graphics_GetAveStrWidth(hdc, 2);
+ if (-1 == self->leftBlockMinWidth)
+ self->leftBlockMinWidth = Graphics_GetAveStrWidth(hdc, 16);
+
+ text = StatusBar_GetTextBlockInfo(&record->rightBlock, hdc);
+ if (FALSE == IS_STRING_EMPTY(text))
+ {
+ CopyRect(&textRect, &clientRect);
+ textRect.left = textRect.right - record->rightBlock.width;
+ limit = clientRect.left + self->spacing + self->leftBlockMinWidth;
+ if (textRect.left < limit)
+ textRect.left = limit;
+
+
+ if (textRect.left < textRect.right)
+ {
+ DrawTextEx(hdc, (wchar_t*)text, -1, &textRect,
+ DT_LEFT | DT_BOTTOM | DT_TABSTOP | DT_SINGLELINE |
+ DT_NOPREFIX | DT_EXPANDTABS, &textParams);
+ }
+ }
+
+ text = StatusBar_GetTextBlockInfo(&record->mainBlock, hdc);
+ if (FALSE == IS_STRING_EMPTY(text))
+ {
+ CopyRect(&textRect, &clientRect);
+ textRect.right = textRect.left + record->mainBlock.width;
+ limit = clientRect.right - record->rightBlock.width - 8;
+ if (limit < self->leftBlockMinWidth)
+ limit = self->leftBlockMinWidth;
+ if (textRect.right > limit)
+ textRect.right = limit;
+
+
+ if (textRect.left < textRect.right)
+ {
+ DrawTextEx(hdc, (wchar_t*)text, -1, &textRect,
+ DT_LEFT | DT_BOTTOM | DT_TABSTOP | DT_SINGLELINE |
+ DT_NOPREFIX | DT_EXPANDTABS | DT_END_ELLIPSIS, &textParams);
+ }
+ }
+
+
+
+ SetBkColor(hdc, prevBackColor);
+ SetTextColor(hdc, prevTextColor);
+ SelectFont(hdc, prevFont);
+ }
+
+ return;
+}
+
+static void
+StatusBar_PaintBuffered(HWND hwnd, HDC hdc, const RECT *paintRect, BOOL erase)
+{
+ StatusBar *self;
+ HDC bufferDC, targetDC;
+ RECT clientRect;
+
+ STATUSBAR_RET_VOID(self, hwnd);
+
+ GetClientRect(hwnd, &clientRect);
+
+ if (FALSE != BackBuffer_EnsureSizeEx(&self->backBuffer,
+ RECTWIDTH(*paintRect), RECTHEIGHT(*paintRect),
+ RECTWIDTH(clientRect), RECTHEIGHT(clientRect)))
+ {
+
+ bufferDC = BackBuffer_GetDC(&self->backBuffer);
+ if (NULL != bufferDC)
+ {
+ SetViewportOrgEx(bufferDC, -paintRect->left, -paintRect->top, NULL);
+ targetDC = hdc;
+ hdc = bufferDC;
+ }
+ }
+ else
+ {
+ bufferDC = NULL;
+ targetDC = NULL;
+ }
+
+ StatusBar_Paint(hwnd, hdc, paintRect, erase);
+
+ if (NULL != bufferDC)
+ {
+ hdc = targetDC;
+
+ SetViewportOrgEx(bufferDC, 0, 0, NULL);
+
+ BackBuffer_Copy(&self->backBuffer, hdc,
+ paintRect->left, paintRect->top, RECTWIDTH(*paintRect), RECTHEIGHT(*paintRect));
+ }
+ return;
+}
+
+static BOOL
+StatusBar_InvalidateByIndex(StatusBar *self, HWND hwnd, size_t recordIndex)
+{
+ unsigned int windowStyle;
+ size_t visibleIndex;
+
+ windowStyle = GetWindowStyle(hwnd);
+ if (0 == (WS_VISIBLE & windowStyle))
+ return FALSE;
+
+ if(NULL != StatusBar_FindVisibleRecord(self, &visibleIndex))
+ {
+ if (recordIndex == visibleIndex)
+ return InvalidateRect(hwnd, NULL, FALSE);
+ }
+
+ return FALSE;
+
+}
+
+static BOOL
+StatusBar_InvalidateByRecord(StatusBar *self, HWND hwnd, StatusRecord *record)
+{
+ unsigned int windowStyle;
+ StatusRecord *visibleRecord;
+
+ windowStyle = GetWindowStyle(hwnd);
+ if (0 == (WS_VISIBLE & windowStyle))
+ return FALSE;
+
+ visibleRecord = StatusBar_FindVisibleRecord(self, NULL);
+ if (record == visibleRecord &&
+ NULL != visibleRecord)
+ {
+ return InvalidateRect(hwnd, NULL, FALSE);
+ }
+ return FALSE;
+}
+
+
+static LRESULT
+StatusBar_OnCreate(HWND hwnd, CREATESTRUCT *createStruct)
+{
+ StatusBar *self;
+
+ self = new StatusBar();
+ if (NULL == self)
+ return -1;
+
+ SetLastError(ERROR_SUCCESS);
+ if (!SetWindowLongPtr(hwnd, 0, (LONGX86)self) && ERROR_SUCCESS != GetLastError())
+ {
+ delete self;
+ return -1;
+ }
+
+ if (NULL != createStruct->lpszName)
+ StatusBar_AddRecord(self, createStruct->lpszName);
+
+ self->backBrush = GetSysColorBrush(COLOR_WINDOW);
+ self->backColor = GetSysColor(COLOR_WINDOW);
+ self->textColor = GetSysColor(COLOR_WINDOWTEXT);
+ self->idealHeight = -1;
+ self->spacing = -1;
+ self->leftBlockMinWidth = -1;
+
+
+ BackBuffer_Initialize(&self->backBuffer, hwnd);
+
+ return 0;
+}
+
+static void
+StatusBar_OnDestroy(HWND hwnd)
+{
+ StatusBar *self;
+ size_t index;
+ StatusRecord *record;
+
+ self = STATUSBAR(hwnd);
+ SetWindowLongPtr(hwnd, 0, 0);
+
+ if (NULL == self)
+ return;
+
+ index = self->list.size();
+ while(index--)
+ {
+ record = self->list[index];
+ StatusBar_FreeTextBlock(&record->mainBlock);
+ StatusBar_FreeTextBlock(&record->rightBlock);
+ }
+
+ BackBuffer_Uninitialize(&self->backBuffer);
+
+ delete self;
+}
+
+static void
+StatusBar_OnPaint(HWND hwnd)
+{
+ PAINTSTRUCT ps;
+
+ if (NULL != BeginPaint(hwnd, &ps))
+ {
+ StatusBar_PaintBuffered(hwnd, ps.hdc, &ps.rcPaint, ps.fErase);
+ EndPaint(hwnd, &ps);
+ }
+}
+
+static void
+StatusBar_OnPrintClient(HWND hwnd, HDC hdc, UINT options)
+{
+ RECT clientRect;
+ if (GetClientRect(hwnd, &clientRect))
+ {
+ StatusBar_Paint(hwnd, hdc, &clientRect, TRUE);
+ }
+}
+
+static void
+StatusBar_OnWindowPosChanged(HWND hwnd, WINDOWPOS *windowPos)
+{
+ if (SWP_NOSIZE != ((SWP_NOSIZE | SWP_FRAMECHANGED) & windowPos->flags) &&
+ 0 == (SWP_NOREDRAW & windowPos->flags))
+ {
+ InvalidateRect(hwnd, NULL, FALSE);
+ }
+}
+
+static LRESULT
+StatusBar_OnSetText(HWND hwnd, LPCWSTR text)
+{
+ StatusBar *self;
+ StatusRecord *record;
+ size_t count;
+
+ STATUSBAR_RET_VAL(self, hwnd, FALSE);
+
+ count = self->list.size();
+ if (0 == count)
+ {
+ if (NULL == text)
+ return TRUE;
+
+ record = StatusBar_AddRecord(self, text);
+ if (NULL == record)
+ return FALSE;
+ }
+ else
+ {
+ record = self->list[count - 1];
+ StatusBar_FreeTextBlock(&record->mainBlock);
+ StatusBar_FreeTextBlock(&record->rightBlock);
+
+ record->mainBlock.text = ResourceString_Duplicate(text);
+ }
+
+ StatusBar_InvalidateByRecord(self, hwnd, record);
+
+ return TRUE;
+}
+
+static LRESULT
+StatusBar_OnGetText(HWND hwnd, LPWSTR buffer, size_t bufferMax)
+{
+ StatusBar *self;
+ StatusRecord *record;
+ const wchar_t *lText, *rText;
+ size_t count, remaining;
+ wchar_t *cursor;
+ HRESULT hr;
+
+ STATUSBAR_RET_VAL(self, hwnd, 0);
+
+ if (NULL == buffer)
+ return 0;
+
+ count = self->list.size();
+ if (0 == count)
+ {
+ lText = NULL;
+ rText = NULL;
+ }
+ else
+ {
+ record = self->list[count - 1];
+ lText = StatusBar_RetriveText(&record->mainBlock);
+ rText = StatusBar_RetriveText(&record->rightBlock);
+ }
+
+ cursor = buffer;
+ remaining = bufferMax;
+
+ if (NULL != lText)
+ hr = StringCchCopyEx(cursor, bufferMax, lText, &cursor, &remaining, 0);
+ else
+ hr = S_OK;
+
+ if (NULL != rText && SUCCEEDED(hr))
+ {
+ hr = StringCchCopyEx(cursor, bufferMax, L"\f", &cursor, &remaining, 0);
+ if (SUCCEEDED(hr))
+ hr = StringCchCopyEx(cursor, bufferMax, rText, &cursor, &remaining, 0);
+ }
+
+ return (bufferMax - remaining);
+}
+
+static LRESULT
+StatusBar_OnGetTextLength(HWND hwnd)
+{
+ StatusBar *self;
+ StatusRecord *record;
+ size_t length, r;
+
+ STATUSBAR_RET_VAL(self, hwnd, 0);
+
+ length = self->list.size();
+ if (0 == length)
+ return 0;
+
+ record = self->list[length - 1];
+ length = StatusBar_GetTextBlockLength(&record->mainBlock);
+ r = StatusBar_GetTextBlockLength(&record->rightBlock);
+ if (0 != r)
+ {
+ length += (r + 1);
+ }
+
+ return length;
+}
+
+
+static void
+StatusBar_OnSetFont(HWND hwnd, HFONT font, BOOL redraw)
+{
+ StatusBar *self;
+ StatusRecord *record;
+ size_t index;
+
+ STATUSBAR_RET_VOID(self, hwnd);
+
+ self->font = font;
+ self->idealHeight = -1;
+ self->spacing = -1;
+ self->leftBlockMinWidth = -1;
+
+ index = self->list.size();
+ while(index--)
+ {
+ record = self->list[index];
+ record->mainBlock.width = -1;
+ record->rightBlock.width = -1;
+ }
+
+ if (NULL != redraw)
+ InvalidateRect(hwnd, NULL, TRUE);
+}
+
+static LRESULT
+StatusBar_OnGetFont(HWND hwnd)
+{
+ StatusBar *self;
+ STATUSBAR_RET_VAL(self, hwnd, NULL);
+
+ return (LRESULT)self->font;
+}
+
+static LRESULT
+StatusBar_OnSetBackBrush(HWND hwnd, HBRUSH brush, BOOL redraw)
+{
+ StatusBar *self;
+ HBRUSH prevBrush;
+
+ STATUSBAR_RET_VAL(self, hwnd, (LRESULT)GetSysColorBrush(COLOR_WINDOW));
+
+ prevBrush = self->backBrush;
+ self->backBrush = brush;
+
+ if (NULL != redraw)
+ InvalidateRect(hwnd, NULL, TRUE);
+
+ return (LRESULT)prevBrush;
+}
+
+static LRESULT
+StatusBar_OnGetBackBrush(HWND hwnd)
+{
+ StatusBar *self;
+
+ STATUSBAR_RET_VAL(self, hwnd, (LRESULT)GetSysColorBrush(COLOR_WINDOW));
+
+ return (LRESULT)self->backBrush;
+}
+
+static LRESULT
+StatusBar_OnSetBackColor(HWND hwnd, COLORREF color, BOOL redraw)
+{
+ StatusBar *self;
+ COLORREF prevColor;
+
+ STATUSBAR_RET_VAL(self, hwnd, GetSysColor(COLOR_WINDOW));
+
+ prevColor = self->backColor;
+ self->backColor = color;
+
+ if (NULL != redraw)
+ InvalidateRect(hwnd, NULL, FALSE);
+
+ return (LRESULT)prevColor;
+}
+
+static LRESULT
+StatusBar_OnGetBackColor(HWND hwnd)
+{
+ StatusBar *self;
+ STATUSBAR_RET_VAL(self, hwnd, GetSysColor(COLOR_WINDOW));
+
+ return (LRESULT)self->backColor;
+
+}
+
+static LRESULT
+StatusBar_OnSetTextColor(HWND hwnd, COLORREF color, BOOL redraw)
+{
+ StatusBar *self;
+ COLORREF prevColor;
+
+ STATUSBAR_RET_VAL(self, hwnd, GetSysColor(COLOR_WINDOWTEXT));
+
+ prevColor = self->textColor;
+ self->textColor = color;
+
+ if (NULL != redraw)
+ InvalidateRect(hwnd, NULL, FALSE);
+
+ return (LRESULT)prevColor;
+}
+
+static LRESULT
+StatusBar_OnGetTextColor(HWND hwnd)
+{
+ StatusBar *self;
+ STATUSBAR_RET_VAL(self, hwnd, GetSysColor(COLOR_WINDOWTEXT));
+
+ return (LRESULT)self->textColor;
+}
+
+static LRESULT
+StatusBar_OnGetIdealHeight(HWND hwnd)
+{
+ StatusBar *self;
+ STATUSBAR_RET_VAL(self, hwnd, 0);
+
+ if (self->idealHeight < 0)
+ {
+ HDC hdc;
+
+ self->idealHeight = 0;
+
+ hdc = GetDCEx(hwnd, NULL, DCX_CACHE | DCX_NORESETATTRS);
+ if (NULL != hdc)
+ {
+ TEXTMETRIC metrics;
+ HFONT font, prevFont;
+
+ font = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0L);
+ prevFont = SelectFont(hdc, font);
+
+ if (FALSE != GetTextMetrics(hdc, &metrics))
+ self->idealHeight = metrics.tmHeight;
+
+ SelectFont(hdc, prevFont);
+ ReleaseDC(hwnd, hdc);
+ }
+ }
+
+ return (LRESULT)self->idealHeight;
+
+}
+static LRESULT
+StatusBar_OnAddStatus(HWND hwnd, const wchar_t *text)
+{
+ StatusBar *self;
+ StatusRecord *record;
+
+ STATUSBAR_RET_VAL(self, hwnd, STATUS_ERROR);
+
+ record = StatusBar_AddRecord(self, text);
+ if (NULL == record)
+ return STATUS_ERROR;
+
+ StatusBar_InvalidateByRecord(self, hwnd, record);
+
+ return record->id;
+}
+
+static LRESULT
+StatusBar_OnRemoveStatus(HWND hwnd, unsigned int statusId)
+{
+ StatusBar *self;
+ StatusRecord *record;
+ size_t index;
+ STATUSBAR_RET_VAL(self, hwnd, FALSE);
+
+ record = StatusBar_FindRecord(self, statusId, &index);
+ if (NULL == record)
+ return FALSE;
+
+ StatusBar_InvalidateByRecord(self, hwnd, record);
+
+ self->list.erase(self->list.begin() + index);
+
+ StatusBar_FreeTextBlock(&record->mainBlock);
+ StatusBar_FreeTextBlock(&record->rightBlock);
+
+ free(record);
+
+ return (LRESULT)TRUE;
+}
+
+static LRESULT
+StatusBar_OnSetStatusText(HWND hwnd, unsigned int statusId, const wchar_t *text)
+{
+ StatusBar *self;
+ StatusRecord *record;
+ size_t index;
+
+ STATUSBAR_RET_VAL(self, hwnd, FALSE);
+
+ record = StatusBar_FindRecord(self, statusId, &index);
+ if (NULL == record)
+ return FALSE;
+
+ StatusBar_InvalidateByRecord(self, hwnd, record);
+
+ StatusBar_FreeTextBlock(&record->mainBlock);
+ record->mainBlock.text = ResourceString_Duplicate(text);
+
+ StatusBar_InvalidateByRecord(self, hwnd, record);
+
+ return (LRESULT)TRUE;
+}
+
+static LRESULT
+StatusBar_OnSetStatusRightText(HWND hwnd, unsigned int statusId, const wchar_t *text)
+{
+ StatusBar *self;
+ StatusRecord *record;
+ size_t index;
+
+ STATUSBAR_RET_VAL(self, hwnd, FALSE);
+
+ record = StatusBar_FindRecord(self, statusId, &index);
+ if (NULL == record)
+ return FALSE;
+
+ StatusBar_InvalidateByRecord(self, hwnd, record);
+
+ StatusBar_FreeTextBlock(&record->rightBlock);
+ record->rightBlock.text = ResourceString_Duplicate(text);
+
+ StatusBar_InvalidateByRecord(self, hwnd, record);
+
+ return (LRESULT)TRUE;
+}
+
+
+static LRESULT
+StatusBar_OnMoveStatus(HWND hwnd, unsigned int statusId, unsigned int moveCommand)
+{
+ StatusBar *self;
+ StatusRecord *record;
+ size_t index, count, newIndex;
+
+ STATUSBAR_RET_VAL(self, hwnd, FALSE);
+
+ record = StatusBar_FindRecord(self, statusId, &index);
+ if (NULL == record)
+ return FALSE;
+
+ count = self->list.size();
+
+ switch(moveCommand)
+ {
+
+ case STATUS_MOVE_BOTTOM:
+ if (0 == index)
+ return TRUE;
+ self->list.erase(self->list.begin() + index);
+ self->list.insert(self->list.begin(), record);
+ newIndex = 0;
+ break;
+
+ case STATUS_MOVE_TOP:
+ if (index == (count - 1))
+ return TRUE;
+
+ self->list.erase(self->list.begin() + index);
+ self->list.push_back(record);
+ newIndex = count - 1;
+ break;
+
+ case STATUS_MOVE_UP:
+ if (index == (count - 1))
+ return TRUE;
+
+ self->list.erase(self->list.begin() + index);
+ newIndex = index + 1;
+ self->list.insert(self->list.begin() + newIndex, record);
+ break;
+
+ case STATUS_MOVE_DOWN:
+ if (index == 0)
+ return TRUE;
+
+ self->list.erase(self->list.begin() + index);
+ newIndex = index - 1;
+ self->list.insert(self->list.begin() + newIndex, record);
+ break;
+ }
+
+ if (index != newIndex)
+ {
+ StatusBar_InvalidateByIndex(self, hwnd, index);
+ StatusBar_InvalidateByIndex(self, hwnd, newIndex);
+ }
+
+ return (LRESULT)TRUE;
+}
+
+static LRESULT CALLBACK
+StatusBar_WindowProc(HWND hwnd, unsigned int uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_CREATE: return StatusBar_OnCreate(hwnd, (CREATESTRUCT*)lParam);
+ case WM_DESTROY: StatusBar_OnDestroy(hwnd); return 0;
+ case WM_PAINT: StatusBar_OnPaint(hwnd); return 0;
+ case WM_PRINTCLIENT: StatusBar_OnPrintClient(hwnd, (HDC)wParam, (UINT)lParam); return 0;
+ case WM_PRINT: return 0;
+ case WM_ERASEBKGND: return 0;
+ case WM_WINDOWPOSCHANGED: StatusBar_OnWindowPosChanged(hwnd, (WINDOWPOS*)lParam); return 0;
+ case WM_SIZE: return 0;
+ case WM_MOVE: return 0;
+ case WM_SETTEXT: return StatusBar_OnSetText(hwnd, (LPCWSTR)lParam);
+ case WM_GETTEXT: return StatusBar_OnGetText(hwnd, (LPWSTR)lParam, (INT)wParam);
+ case WM_GETTEXTLENGTH: return StatusBar_OnGetTextLength(hwnd);
+ case WM_SETFONT: StatusBar_OnSetFont(hwnd, (HFONT)wParam, (BOOL)LOWORD(lParam)); return 0;
+ case WM_GETFONT: return StatusBar_OnGetFont(hwnd);
+
+ case STATUSBAR_WM_SET_BACK_BRUSH: return StatusBar_OnSetBackBrush(hwnd, (HBRUSH)lParam, (BOOL)wParam);
+ case STATUSBAR_WM_GET_BACK_BRUSH: return StatusBar_OnGetBackBrush(hwnd);
+ case STATUSBAR_WM_SET_BACK_COLOR: return StatusBar_OnSetBackColor(hwnd, (COLORREF)lParam, (BOOL)wParam);
+ case STATUSBAR_WM_GET_BACK_COLOR: return StatusBar_OnGetBackColor(hwnd);
+ case STATUSBAR_WM_SET_TEXT_COLOR: return StatusBar_OnSetTextColor(hwnd, (COLORREF)lParam, (BOOL)wParam);
+ case STATUSBAR_WM_GET_TEXT_COLOR: return StatusBar_OnGetTextColor(hwnd);
+ case STATUSBAR_WM_GET_IDEAL_HEIGHT: return StatusBar_OnGetIdealHeight(hwnd);
+ case STATUSBAR_WM_ADD_STATUS: return StatusBar_OnAddStatus(hwnd, (const wchar_t*)lParam);
+ case STATUSBAR_WM_REMOVE_STATUS: return StatusBar_OnRemoveStatus(hwnd, (unsigned int)(wParam));
+ case STATUSBAR_WM_SET_STATUS_TEXT: return StatusBar_OnSetStatusText(hwnd, (unsigned int)(wParam), (const wchar_t*)lParam);
+ case STATUSBAR_WM_SET_STATUS_RTEXT: return StatusBar_OnSetStatusRightText(hwnd, (unsigned int)(wParam), (const wchar_t*)lParam);
+ case STATUSBAR_WM_MOVE_STATUS: return StatusBar_OnMoveStatus(hwnd, (unsigned int)(wParam), (unsigned int)lParam);
+ }
+
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/statusBar.h b/Src/Plugins/Library/ml_devices/statusBar.h
new file mode 100644
index 00000000..692d6d46
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/statusBar.h
@@ -0,0 +1,81 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_STATUS_BAR_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_STATUS_BAR_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+
+#define STATUSBAR_WINDOW_CLASS L"NullsoftDevicesStatusBar"
+
+#define STATUS_ERROR ((unsigned int)-1)
+
+HWND
+StatusBar_CreateWindow(unsigned long windowExStyle,
+ const wchar_t *text,
+ unsigned long windowStyle,
+ int x,
+ int y,
+ int width,
+ int height,
+ HWND parentWindow,
+ unsigned int controlId);
+
+#define STATUSBAR_WM_FIRST (WM_USER + 10)
+
+#define STATUSBAR_WM_SET_BACK_BRUSH (STATUSBAR_WM_FIRST + 0)
+#define STATUSBAR_SET_BACK_BRUSH(/*HWND*/ _hwnd, /*HBRSUSH*/ _brush, /*BOOL*/_redraw)\
+ ((HBRUSH)SendMessageW((_hwnd), STATUSBAR_WM_SET_BACK_BRUSH, (WPARAM)(_redraw), (LPARAM)(_brush)))
+
+#define STATUSBAR_WM_GET_BACK_BRUSH (STATUSBAR_WM_FIRST + 1)
+#define STATUSBAR_GET_BRUSH(/*HWND*/ _hwnd)\
+ ((HBRUSH)SendMessageW((_hwnd), STATUSBAR_WM_GET_BRUSH, 0, 0L))
+
+#define STATUSBAR_WM_SET_BACK_COLOR (STATUSBAR_WM_FIRST + 2)
+#define STATUSBAR_SET_BACK_COLOR(/*HWND*/ _hwnd, /*COLORREF*/ _color, /*BOOL*/_redraw)\
+ ((COLORREF)SendMessageW((_hwnd), STATUSBAR_WM_SET_BACK_COLOR, (WPARAM)(_redraw), (LPARAM)(_color)))
+
+#define STATUSBAR_WM_GET_BACK_COLOR (STATUSBAR_WM_FIRST + 3)
+#define STATUSBAR_GET_BACK_COLOR(/*HWND*/ _hwnd)\
+ ((COLORREF)SendMessageW((_hwnd), STATUSBAR_WM_GET_BACK_COLOR, 0, 0L))
+
+#define STATUSBAR_WM_SET_TEXT_COLOR (STATUSBAR_WM_FIRST + 4)
+#define STATUSBAR_SET_TEXT_COLOR(/*HWND*/ _hwnd, /*COLORREF*/ _color, /*BOOL*/_redraw)\
+ ((COLORREF)SendMessageW((_hwnd), STATUSBAR_WM_SET_TEXT_COLOR, (WPARAM)(_redraw), (LPARAM)(_color)))
+
+#define STATUSBAR_WM_GET_TEXT_COLOR (STATUSBAR_WM_FIRST + 5)
+#define STATUSBAR_GET_TEXT_COLOR(/*HWND*/ _hwnd)\
+ ((COLORREF)SendMessageW((_hwnd), STATUSBAR_WM_GET_TEXT_COLOR, 0, 0L))
+
+#define STATUSBAR_WM_GET_IDEAL_HEIGHT (STATUSBAR_WM_FIRST + 6)
+#define STATUSBAR_GET_IDEAL_HEIGHT(/*HWND*/ _hwnd)\
+ ((int)SendMessageW((_hwnd), STATUSBAR_WM_GET_IDEAL_HEIGHT, 0, 0L))
+
+#define STATUSBAR_WM_ADD_STATUS (STATUSBAR_WM_FIRST + 7)
+#define STATUSBAR_ADD_STATUS(/*HWND*/ _hwnd, /*const wchar_t* */ _statusText)\
+ ((unsigned int)SendMessageW((_hwnd), STATUSBAR_WM_ADD_STATUS, 0, (LPARAM)(_statusText)))
+
+#define STATUSBAR_WM_REMOVE_STATUS (STATUSBAR_WM_FIRST + 8)
+#define STATUSBAR_REMOVE_STATUS(/*HWND*/ _hwnd, /*unsigned int*/ _statusId)\
+ ((BOOL)SendMessageW((_hwnd), STATUSBAR_WM_REMOVE_STATUS, (WPARAM)(_statusId), 0L))
+
+#define STATUSBAR_WM_SET_STATUS_TEXT (STATUSBAR_WM_FIRST + 9)
+#define STATUSBAR_SET_STATUS_TEXT(/*HWND*/ _hwnd, /*unsigned int*/ _statusId, /*const wchar_t* */ _statusText)\
+ ((BOOL)SendMessageW((_hwnd), STATUSBAR_WM_SET_STATUS_TEXT, (WPARAM)(_statusId), (LPARAM)(_statusText)))
+
+#define STATUSBAR_WM_SET_STATUS_RTEXT (STATUSBAR_WM_FIRST + 10)
+#define STATUSBAR_SET_STATUS_RTEXT(/*HWND*/ _hwnd, /*unsigned int*/ _statusId, /*const wchar_t* */ _statusText)\
+ ((BOOL)SendMessageW((_hwnd), STATUSBAR_WM_SET_STATUS_RTEXT, (WPARAM)(_statusId), (LPARAM)(_statusText)))
+
+#define STATUS_MOVE_TOP 0
+#define STATUS_MOVE_BOTTOM 1
+#define STATUS_MOVE_UP 2
+#define STATUS_MOVE_DOWN 3
+
+#define STATUSBAR_WM_MOVE_STATUS (STATUSBAR_WM_FIRST + 11)
+#define STATUSBAR_MOVE_STATUS(/*HWND*/ _hwnd, /*unsigned int*/ _statusId, /*unsigned int */ _statusMove)\
+ ((BOOL)SendMessageW((_hwnd), STATUSBAR_WM_MOVE_STATUS, (WPARAM)(_statusId), (LPARAM)(_statusMove)))
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_STATUS_BAR_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/strings.cpp b/Src/Plugins/Library/ml_devices/strings.cpp
new file mode 100644
index 00000000..bfbef052
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/strings.cpp
@@ -0,0 +1,195 @@
+#include "main.h"
+#include "./strings.h"
+#include <strsafe.h>
+
+
+wchar_t *
+String_Malloc(size_t size)
+{
+ return (wchar_t *)malloc(sizeof(wchar_t) * size);
+}
+
+wchar_t *
+String_ReAlloc(wchar_t *string, size_t size)
+{
+ return (wchar_t *)realloc(string, sizeof(wchar_t) * size);
+}
+
+void
+String_Free(wchar_t *string)
+{
+ if (NULL != string)
+ free(string);
+}
+
+wchar_t *
+String_Duplicate(const wchar_t *string)
+{
+ int length;
+ wchar_t *copy;
+
+ if (NULL == string)
+ return NULL;
+
+ length = lstrlenW(string) + 1;
+
+ copy = String_Malloc(length);
+ if (NULL != copy)
+ CopyMemory(copy, string, sizeof(wchar_t) * length);
+
+ return copy;
+}
+
+
+char *
+String_ToAnsi(unsigned int codePage, unsigned long flags, const wchar_t *string,
+ int stringLength, const char *defaultChar, BOOL *usedDefaultChar)
+{
+ char *buffer;
+ int bufferSize;
+
+ if (stringLength < 0)
+ stringLength = lstrlen(string);
+
+ bufferSize = WideCharToMultiByte(codePage, flags, string, stringLength,
+ NULL, 0, defaultChar, usedDefaultChar);
+ if (0 == bufferSize)
+ return NULL;
+
+ buffer = AnsiString_Malloc(bufferSize + 1);
+ if (NULL == buffer)
+ return NULL;
+
+ bufferSize = WideCharToMultiByte(codePage, flags, string, stringLength,
+ buffer, bufferSize, defaultChar, usedDefaultChar);
+ if (0 == bufferSize)
+ {
+ AnsiString_Free(buffer);
+ return NULL;
+ }
+ buffer[bufferSize] = '\0';
+ return buffer;
+}
+
+size_t
+String_CopyTo(wchar_t *destination, const wchar_t *source, size_t size)
+{
+ size_t remaining;
+ if (FAILED(StringCchCopyExW(destination, size, source, NULL, &remaining, STRSAFE_IGNORE_NULLS)))
+ return 0;
+
+ return (size - remaining);
+}
+
+
+char *
+AnsiString_Malloc(size_t size)
+{
+ return (char*)malloc(sizeof(char) * size);
+}
+
+char *
+AnsiString_ReAlloc(char *string, size_t size)
+{
+ return (char*)realloc(string, sizeof(char) * size);
+}
+
+void
+AnsiString_Free(char *string)
+{
+ if (NULL != string)
+ free(string);
+}
+
+char *
+AnsiString_Duplicate(const char *string)
+{
+ char *copy;
+ INT length;
+
+ if (NULL == string)
+ return NULL;
+
+ length = lstrlenA(string) + 1;
+
+ copy = AnsiString_Malloc(length);
+ if (NULL != copy)
+ CopyMemory(copy, string, sizeof(char) * length);
+
+ return copy;
+}
+
+
+wchar_t *
+AnsiString_ToUnicode(unsigned int codePage, unsigned long flags, const char* string, INT stringLength)
+{
+ wchar_t *buffer;
+ int buffferSize;
+
+ if (NULL == string)
+ return NULL;
+
+ buffferSize = MultiByteToWideChar(codePage, flags, string, stringLength, NULL, 0);
+ if (0 == buffferSize)
+ return NULL;
+
+ if (stringLength > 0)
+ buffferSize++;
+
+ buffer = String_Malloc(buffferSize);
+ if (NULL == buffer)
+ return NULL;
+
+ if (0 == MultiByteToWideChar(codePage, flags, string, stringLength, buffer, buffferSize))
+ {
+ String_Free(buffer);
+ return NULL;
+ }
+
+ if (stringLength > 0)
+ buffer[buffferSize - 1] = L'\0';
+
+ return buffer;
+}
+
+wchar_t*
+ResourceString_Duplicate(const wchar_t *source)
+{
+ return (FALSE != IS_INTRESOURCE(source)) ?
+ (LPWSTR)source :
+ String_Duplicate(source);
+}
+
+void
+ResourceString_Free(wchar_t *string)
+{
+ if (FALSE == IS_INTRESOURCE(string))
+ String_Free(string);
+}
+
+size_t
+ResourceString_CopyTo(wchar_t *destination, const wchar_t *source, size_t size)
+{
+ if (NULL == destination)
+ return 0;
+
+ if (NULL == source)
+ {
+ destination[0] = L'\0';
+ return 0;
+ }
+
+ if (FALSE != IS_INTRESOURCE(source))
+ {
+ if (NULL == WASABI_API_LNG)
+ {
+ destination[0] = L'\0';
+ return 0;
+ }
+
+ WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)source, destination, size);
+ return lstrlenW(destination);
+ }
+
+ return String_CopyTo(destination, source, size);
+}
diff --git a/Src/Plugins/Library/ml_devices/strings.h b/Src/Plugins/Library/ml_devices/strings.h
new file mode 100644
index 00000000..52ddfd21
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/strings.h
@@ -0,0 +1,74 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_STRINGS_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_STRINGS_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#define IS_STRING_EMPTY(_string) (NULL == (_string) || L'\0' == *(_string))
+
+wchar_t *
+String_Malloc(size_t size);
+
+wchar_t *
+String_ReAlloc(wchar_t *string,
+ size_t size);
+
+void
+String_Free(wchar_t *string);
+
+wchar_t *
+String_Duplicate(const wchar_t *string);
+
+char *
+String_ToAnsi(unsigned int codePage,
+ unsigned long flags,
+ const wchar_t *string,
+ int stringLength,
+ const char *defaultChar,
+ BOOL *usedDefaultChar);
+
+size_t
+String_CopyTo(wchar_t *destination,
+ const wchar_t *source,
+ size_t size);
+/*
+ Ansi String
+*/
+
+char *
+AnsiString_Malloc(size_t size);
+
+char *
+AnsiString_ReAlloc(char *string,
+ size_t size);
+
+void
+AnsiString_Free(char *string);
+
+char *
+AnsiString_Duplicate(const char *string);
+
+wchar_t *
+AnsiString_ToUnicode(unsigned int codePage,
+ unsigned long flags,
+ const char *string,
+ int stringLength);
+
+/*
+ Resource String
+*/
+
+wchar_t*
+ResourceString_Duplicate(const wchar_t *source);
+
+void
+ResourceString_Free(wchar_t *string);
+
+size_t
+ResourceString_CopyTo(wchar_t *destination,
+ const wchar_t *source,
+ size_t size);
+
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_PLUGIN_STRINGS_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/version.rc2 b/Src/Plugins/Library/ml_devices/version.rc2
new file mode 100644
index 00000000..a410e817
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,35,0,0
+ PRODUCTVERSION WINAMP_PRODUCTVER
+ FILEFLAGSMASK 0x17L
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x4L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "CompanyName", "Winamp SA"
+ VALUE "FileDescription", "Winamp Media Library Plug-in"
+ VALUE "FileVersion", "1,35,0,0"
+ VALUE "InternalName", "Nullsoft Device Manager"
+ VALUE "LegalCopyright", "Copyright © 2010-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_devices.dll"
+ VALUE "ProductName", "Winamp"
+ VALUE "ProductVersion", STR_WINAMP_PRODUCTVER
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/Src/Plugins/Library/ml_devices/wasabi.cpp b/Src/Plugins/Library/ml_devices/wasabi.cpp
new file mode 100644
index 00000000..f71f4bed
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/wasabi.cpp
@@ -0,0 +1,107 @@
+#include "main.h"
+#include "./wasabi.h"
+
+#include <api/service/waservicefactory.h>
+
+api_application *WASABI_API_APP = NULL;
+api_language *WASABI_API_LNG = NULL;
+api_devicemanager *WASABI_API_DEVICES = NULL;
+api_winamp *WASABI_API_WINAMP = NULL;
+
+HINSTANCE WASABI_API_LNG_HINST = NULL;
+HINSTANCE WASABI_API_ORIG_HINST = NULL;
+
+static unsigned long wasabiReference = 0;
+static BOOL defaultServicesLoaded = FALSE;
+EXTERN_C winampMediaLibraryPlugin plugin;
+
+static void Wasabi_Uninitialize()
+{
+ Wasabi_ReleaseInterface(applicationApiServiceGuid, WASABI_API_APP);
+ Wasabi_ReleaseInterface(languageApiGUID, WASABI_API_LNG);
+ Wasabi_ReleaseInterface(DeviceManagerGUID, WASABI_API_DEVICES);
+ Wasabi_ReleaseInterface(winampApiGuid, WASABI_API_WINAMP);
+
+ WASABI_API_APP = NULL;
+ WASABI_API_LNG = NULL;
+ WASABI_API_DEVICES = NULL;
+ WASABI_API_WINAMP = NULL;
+ defaultServicesLoaded = FALSE;
+}
+
+BOOL Wasabi_Initialize(HINSTANCE instance)
+{
+ defaultServicesLoaded = FALSE;
+
+ WASABI_API_APP = NULL;
+ WASABI_API_DEVICES = NULL;
+
+ WASABI_API_LNG = NULL;
+ WASABI_API_ORIG_HINST = instance;
+ WASABI_API_LNG_HINST = WASABI_API_ORIG_HINST;
+
+ Wasabi_AddRef();
+ return TRUE;
+}
+
+BOOL Wasabi_InitializeFromWinamp(HINSTANCE instance, HWND winampWindow)
+{
+ return Wasabi_Initialize(instance);
+}
+
+BOOL Wasabi_LoadDefaultServices(void)
+{
+ if (FALSE != defaultServicesLoaded)
+ return FALSE;
+
+ WASABI_API_APP = Wasabi_QueryInterface(api_application, applicationApiServiceGuid);
+ WASABI_API_DEVICES = Wasabi_QueryInterface(api_devicemanager, DeviceManagerGUID);
+ WASABI_API_WINAMP = Wasabi_QueryInterface(api_winamp, winampApiGuid);
+
+ WASABI_API_LNG = Wasabi_QueryInterface(api_language, languageApiGUID);
+ if (NULL != WASABI_API_LNG)
+ {
+ WASABI_API_LNG_HINST = WASABI_API_LNG->StartLanguageSupport(WASABI_API_ORIG_HINST, MlDevicesLangGUID);
+ }
+
+ defaultServicesLoaded = TRUE;
+ return TRUE;
+}
+
+unsigned long
+Wasabi_AddRef(void)
+{
+ return InterlockedIncrement((LONG*)&wasabiReference);
+}
+
+unsigned long
+Wasabi_Release(void)
+{
+ if (0 == wasabiReference)
+ return wasabiReference;
+
+ LONG r = InterlockedDecrement((LONG*)&wasabiReference);
+ if (0 == r)
+ {
+ Wasabi_Uninitialize();
+ }
+ return r;
+}
+
+void * Wasabi_QueryInterface0(const GUID &interfaceGuid)
+{
+ waServiceFactory *serviceFactory = plugin.service->service_getServiceByGuid(interfaceGuid);
+ if (NULL == serviceFactory)
+ return NULL;
+
+ return serviceFactory->getInterface();
+}
+
+void Wasabi_ReleaseInterface0(const GUID &interfaceGuid, void *interfaceInstance)
+{
+ waServiceFactory *serviceFactory = plugin.service->service_getServiceByGuid(interfaceGuid);
+ if (NULL == serviceFactory)
+ return;
+
+ serviceFactory->releaseInterface(interfaceInstance);
+}
diff --git a/Src/Plugins/Library/ml_devices/wasabi.h b/Src/Plugins/Library/ml_devices/wasabi.h
new file mode 100644
index 00000000..071dc8fd
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/wasabi.h
@@ -0,0 +1,43 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_WASABI_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_WASABI_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <api/application/api_application.h>
+#define WASABI_API_APP applicationApi
+
+#include "../Agave/Language/api_language.h"
+
+#include "../devices/api_devicemanager.h"
+extern api_devicemanager *deviceManagerApi;
+#define WASABI_API_DEVICES deviceManagerApi
+
+#include "../winamp/api_winamp.h"
+extern api_winamp *winampApi;
+#define WASABI_API_WINAMP winampApi
+
+BOOL Wasabi_Initialize(HINSTANCE instance);
+BOOL Wasabi_InitializeFromWinamp(HINSTANCE instance, HWND winampWindow);
+BOOL Wasabi_LoadDefaultServices(void);
+
+unsigned long Wasabi_AddRef(void);
+unsigned long Wasabi_Release(void);
+
+void * Wasabi_QueryInterface0(const GUID &interfaceGuid);
+
+#define Wasabi_QueryInterface(_interfaceType, _interfaceGuid)\
+ ((##_interfaceType*)Wasabi_QueryInterface0(_interfaceGuid))
+
+
+void Wasabi_ReleaseInterface0(const GUID &interfaceGuid, void *interfaceInstance);
+
+#define Wasabi_ReleaseInterface(_interfaceGuid, _interfaceInstance)\
+ (Wasabi_ReleaseInterface0((_interfaceGuid), (_interfaceInstance)))
+
+
+#define Wasabi_ReleaseObject(_object)\
+ {if (NULL != (_object)) {((Dispatchable*)(_object))->Release();}}
+
+#endif // _NULLSOFT_WINAMP_ML_DEVICES_WASABI_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/welcomeWidget.cpp b/Src/Plugins/Library/ml_devices/welcomeWidget.cpp
new file mode 100644
index 00000000..a4ea327b
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/welcomeWidget.cpp
@@ -0,0 +1,760 @@
+#include "main.h"
+#include "./welcomeWidget.h"
+#include "../winamp/commandLink.h"
+
+#include <strsafe.h>
+
+#define WELCOMEWIDGET_OFFSET_LEFT_DLU 4
+#define WELCOMEWIDGET_OFFSET_TOP_DLU 2
+#define WELCOMEWIDGET_OFFSET_RIGHT_DLU 4
+#define WELCOMEWIDGET_OFFSET_BOTTOM_DLU 2
+
+#define WELCOMEWIDGET_IMAGE_MIN_WIDTH 280
+#define WELCOMEWIDGET_IMAGE_MIN_HEIGHT (424 - 200)
+
+#define WELCOMEWIDGET_TITLE_MAX_HEIGHT 168
+#define WELCOMEWIDGET_TITLE_MIN_WIDTH WELCOMEWIDGET_IMAGE_MIN_WIDTH
+
+#define WELCOMEWIDGET_TEXT_MAX_HEIGHT 66
+#define WELCOMEWIDGET_TEXT_MIN_WIDTH WELCOMEWIDGET_IMAGE_MIN_WIDTH
+
+#define WELCOMEWIDGET_HELP_LINK 1000
+
+typedef struct WelcomeWidget
+{
+ HDC context;
+ long contextWidth;
+ long contextHeight;
+ HBITMAP previousBitmap;
+ HFONT helpFont;
+} WelcomeWidget;
+
+static BOOL
+WelcomeWidget_GetBestTextRect(HDC hdc, const wchar_t *text, unsigned int length,
+ RECT *rect, long maxWidth, unsigned int format)
+{
+ RECT testRect;
+ long right;
+
+ if (NULL == rect)
+ return FALSE;
+
+ format |= DT_CALCRECT;
+
+ if (length < 0)
+ length = (NULL != text) ? lstrlen(text) : 0;
+
+ if (0 == length || NULL == text)
+ {
+ rect->right = rect->left;
+ rect->bottom = rect->top;
+ return TRUE;
+ }
+
+
+ SetRect(&testRect, rect->left, rect->top, rect->right, rect->top);
+ maxWidth += rect->left;
+ right = rect->right;
+
+ for(;;)
+ {
+ if (FALSE == DrawText(hdc, text, length, &testRect, format))
+ return FALSE;
+
+ if (testRect.bottom <= rect->bottom || right >= maxWidth)
+ {
+ CopyRect(rect, &testRect);
+ break;
+ }
+
+ long increment = maxWidth - right;
+ if (increment > 60)
+ increment = increment / 4;
+ else if (increment > 4)
+ increment = increment / 2;
+
+ right += increment;
+ testRect.right = right;
+ testRect.bottom = rect->top;
+ }
+
+ return TRUE;
+}
+
+static COLORREF
+WelcomeWidget_GetTextColor(WidgetStyle *style, COLORREF *shadowColor, BOOL titleMode)
+{
+ COLORREF frontColor, backColor;
+ WORD backHue, backLuma, backSat, frontHue, frontLuma, frontSat;
+
+ backColor = WIDGETSTYLE_BACK_COLOR(style);
+ frontColor = WIDGETSTYLE_TEXT_COLOR(style);
+
+ ColorRGBToHLS(backColor, &backHue, &backLuma, &backSat);
+ ColorRGBToHLS(frontColor, &frontHue, &frontLuma, &frontSat);
+
+ if(frontLuma > backLuma)
+ {
+ if (NULL != shadowColor)
+ {
+ if (FALSE != titleMode || backLuma > 180)
+ *shadowColor = Graphics_BlendColors(0x000000, backColor, 102);
+ else
+ *shadowColor = backColor;
+ }
+
+ return Graphics_BlendColors((FALSE != titleMode) ? 0xBBBBBB : 0xEEEEEE, frontColor, 230);
+ }
+
+ if (NULL != shadowColor)
+ {
+ if (FALSE != titleMode || backLuma < 60)
+ *shadowColor = Graphics_BlendColors(0xFFFFFF, backColor, 102);
+ else
+ *shadowColor = backColor;
+ }
+
+ return Graphics_BlendColors((FALSE != titleMode) ? 0x333333 : 0x111111, frontColor, 230);
+}
+
+static HFONT
+WelcomeWidget_CreateTitleFont(WidgetStyle *style, HDC hdc)
+{
+ LOGFONT lf;
+ HFONT textFont;
+ long height, pixelsY;
+
+ pixelsY = GetDeviceCaps(hdc, LOGPIXELSY);
+ height = MulDiv(16, pixelsY, 96);
+
+ textFont = WIDGETSTYLE_TITLE_FONT(style);
+ if (NULL != textFont)
+ {
+ HFONT prevFont;
+ TEXTMETRIC tm;
+
+ prevFont = SelectFont(hdc, textFont);
+ if (FALSE != GetTextMetricsW(hdc, &tm))
+ {
+ long test = MulDiv(tm.tmHeight - tm.tmInternalLeading, 96, pixelsY);
+ test = MulDiv((test + 2), pixelsY, 72);
+ if (test > height)
+ height = test;
+ }
+ SelectFont(hdc, prevFont);
+ }
+
+ lf.lfHeight = -height;
+ lf.lfWidth = 0;
+ lf.lfEscapement = 0;
+ lf.lfOrientation = 0;
+ lf.lfWeight = FW_DONTCARE;
+ lf.lfItalic = FALSE;
+ lf.lfUnderline = FALSE;
+ lf.lfStrikeOut = FALSE;
+ lf.lfCharSet = DEFAULT_CHARSET;
+ lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
+ lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
+ lf.lfQuality = Graphics_GetSysFontQuality();
+ lf.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
+ StringCchCopy(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName), L"Arial");
+
+ return CreateFontIndirect(&lf);
+}
+
+static BOOL
+WelcomeWidget_FillContext(HDC targetDC, long width, long height, HDC sourceDC,
+ HBITMAP backBitmap, long backWidth, long backHeight, WidgetStyle *style)
+{
+ RECT rect, partRect;
+ wchar_t buffer[4096] = {0};
+ int bufferLength;
+ COLORREF textColor, shadowColor;
+ HFONT prevFont;
+
+ if (NULL == targetDC ||
+ NULL == sourceDC ||
+ NULL == style)
+ {
+ return FALSE;
+ }
+
+ SetRect(&rect, 0, 0, width, height);
+
+ FillRect(targetDC, &rect, WIDGETSTYLE_BACK_BRUSH(style));
+
+
+ if (NULL != backBitmap)
+ {
+ Image_AlphaBlend(targetDC, &rect, sourceDC, NULL, 255, backBitmap, NULL,
+ AlphaBlend_AlignCenter | AlphaBlend_AlignVCenter, NULL);
+ }
+
+ SetBkMode(targetDC, TRANSPARENT);
+
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_WELCOMEWIDGET_TITLE, buffer, ARRAYSIZE(buffer));
+ if (L'\0' != buffer[0])
+ {
+ BOOL textMode;
+
+ textMode = TRUE;
+
+ if (CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, buffer, -1, L"WINAMP DEVICE MANAGER", -1))
+ {
+ HBITMAP titleBitmap;
+ titleBitmap = Image_LoadEx(Plugin_GetInstance(), MAKEINTRESOURCE(IDR_WELCOME_WIDGET_TITLE_IMAGE),
+ SRC_TYPE_PNG, ISF_PREMULTIPLY, 0, 0);
+ if (NULL != titleBitmap)
+ {
+ SetRect(&partRect, rect.left, rect.top, rect.right, rect.top + WELCOMEWIDGET_TITLE_MAX_HEIGHT);
+
+ if (FALSE != Image_AlphaBlend(targetDC, &partRect, sourceDC, NULL, 255, titleBitmap, NULL,
+ AlphaBlend_AlignCenter | AlphaBlend_AlignVCenter, NULL))
+ {
+ textMode = FALSE;
+ }
+
+ DeleteObject(titleBitmap);
+ }
+ }
+
+ if (FALSE != textMode)
+ {
+ HFONT titleFont;
+
+ bufferLength = lstrlen(buffer);
+
+ titleFont = WelcomeWidget_CreateTitleFont(style, targetDC);
+
+ textColor = WelcomeWidget_GetTextColor(style, &shadowColor, TRUE);
+ prevFont = SelectFont(targetDC, (NULL != titleFont) ? titleFont : WIDGETSTYLE_TITLE_FONT(style));
+
+ SetRect(&partRect, 0, 0, width, 0);
+
+ if (FALSE != DrawText(targetDC, buffer, bufferLength, &partRect,
+ DT_CALCRECT | DT_LEFT | DT_TOP | DT_WORDBREAK | DT_NOPREFIX | DT_EDITCONTROL | DT_END_ELLIPSIS))
+ {
+ if (partRect.bottom > WELCOMEWIDGET_TITLE_MAX_HEIGHT)
+ {
+ TEXTMETRIC textMetrics;
+ if (FALSE != GetTextMetrics(targetDC, &textMetrics))
+ partRect.bottom = (WELCOMEWIDGET_TITLE_MAX_HEIGHT/textMetrics.tmHeight)*textMetrics.tmHeight;
+ }
+
+ OffsetRect(&partRect,
+ rect.left + (RECTWIDTH(rect) - RECTWIDTH(partRect))/2,
+ rect.top + (WELCOMEWIDGET_TITLE_MAX_HEIGHT - RECTHEIGHT(partRect))/2);
+
+ OffsetRect(&partRect, 0, 2);
+ SetTextColor(targetDC, shadowColor);
+ DrawText(targetDC, buffer, bufferLength, &partRect,
+ DT_CENTER | DT_TOP | DT_WORDBREAK | DT_NOPREFIX | DT_EDITCONTROL | DT_END_ELLIPSIS);
+
+ OffsetRect(&partRect, 0, -2);
+
+ SetTextColor(targetDC, textColor);
+ DrawText(targetDC, buffer, bufferLength, &partRect,
+ DT_CENTER | DT_TOP | DT_WORDBREAK | DT_NOPREFIX | DT_EDITCONTROL | DT_END_ELLIPSIS);
+ }
+
+ SelectFont(targetDC, prevFont);
+ if (NULL != titleFont)
+ DeleteObject(titleFont);
+ }
+ }
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_WELCOMEWIDGET_TEXT, buffer, ARRAYSIZE(buffer));
+ if (L'\0' != buffer[0])
+ {
+ bufferLength = lstrlen(buffer);
+
+ textColor = WelcomeWidget_GetTextColor(style, &shadowColor, FALSE);
+ prevFont = SelectFont(targetDC, WIDGETSTYLE_TEXT_FONT(style));
+
+ SetRect(&partRect, 0, 0, WELCOMEWIDGET_TEXT_MIN_WIDTH, WELCOMEWIDGET_TEXT_MAX_HEIGHT);
+
+ if (FALSE != WelcomeWidget_GetBestTextRect(targetDC, buffer, bufferLength, &partRect, width,
+ DT_LEFT | DT_TOP | DT_WORDBREAK | DT_NOPREFIX | DT_EDITCONTROL | DT_END_ELLIPSIS))
+ {
+ if (partRect.bottom > WELCOMEWIDGET_TEXT_MAX_HEIGHT)
+ {
+ TEXTMETRIC textMetrics;
+ if (FALSE != GetTextMetrics(targetDC, &textMetrics))
+ partRect.bottom = (WELCOMEWIDGET_TEXT_MAX_HEIGHT/textMetrics.tmHeight)*textMetrics.tmHeight;
+ }
+
+ OffsetRect(&partRect,
+ rect.left + (RECTWIDTH(rect) - RECTWIDTH(partRect))/2,
+ rect.bottom - WELCOMEWIDGET_TEXT_MAX_HEIGHT);
+
+ OffsetRect(&partRect, 0, 1);
+ SetTextColor(targetDC, shadowColor);
+ DrawText(targetDC, buffer, bufferLength, &partRect,
+ DT_LEFT | DT_TOP | DT_WORDBREAK | DT_NOPREFIX | DT_EDITCONTROL | DT_END_ELLIPSIS);
+
+ OffsetRect(&partRect, 0, -1);
+
+ SetTextColor(targetDC, textColor);
+ DrawText(targetDC, buffer, bufferLength, &partRect,
+ DT_LEFT | DT_TOP | DT_WORDBREAK | DT_NOPREFIX | DT_EDITCONTROL | DT_END_ELLIPSIS);
+ }
+
+ SelectFont(targetDC, prevFont);
+ }
+
+ return TRUE;
+}
+
+static HDC
+WelcomeWidget_CreateContext(HWND hwnd, WidgetStyle *style, HBITMAP *prevBitmapOut, long *widthOut, long *heightOut)
+{
+
+ HDC windowDC, targetDC, sourceDC;
+ HBITMAP backBitmap, targetBitmap;
+ HBITMAP prevTargetBitmap;
+ long width, height, backWidth, backHeight;
+
+ if (NULL == prevBitmapOut || NULL == style)
+ return NULL;
+
+ windowDC = GetDCEx(hwnd, NULL, DCX_CACHE | DCX_NORESETATTRS);
+ if (NULL == windowDC)
+ return NULL;
+
+ backBitmap = Image_LoadEx(Plugin_GetInstance(), MAKEINTRESOURCE(IDR_WELCOME_WIDGET_IMAGE),
+ SRC_TYPE_PNG, ISF_PREMULTIPLY, 0, 0);
+
+ width = WELCOMEWIDGET_IMAGE_MIN_WIDTH;
+ height = WELCOMEWIDGET_IMAGE_MIN_HEIGHT;
+ backWidth = 0;
+ backHeight = 0;
+
+ if (NULL != backBitmap)
+ {
+ BITMAP bi;
+ if (sizeof(bi) == GetObject(backBitmap, sizeof(bi), &bi))
+ {
+ backWidth = bi.bmWidth;
+ if (backWidth > width)
+ width = backWidth;
+
+ backHeight = ABS(bi.bmHeight);
+ if (backHeight > height)
+ height = backHeight;
+ }
+ }
+
+ targetDC = CreateCompatibleDC(windowDC);
+ sourceDC = CreateCompatibleDC(windowDC);
+ targetBitmap = CreateCompatibleBitmap(windowDC, width, height);
+ prevTargetBitmap = NULL;
+
+ ReleaseDC(hwnd, windowDC);
+
+ if (NULL != targetDC &&
+ NULL != sourceDC &&
+ NULL != targetBitmap)
+ {
+
+ HFONT prevTargetFont;
+ prevTargetBitmap = SelectBitmap(targetDC, targetBitmap);
+ prevTargetFont = GetCurrentFont(targetDC);
+ HBITMAP prevSourceBitmap = GetCurrentBitmap(sourceDC);
+
+ if (FALSE == WelcomeWidget_FillContext(targetDC, width, height, sourceDC,
+ backBitmap, backWidth, backHeight, style))
+ {
+ SelectBitmap(targetDC, prevTargetBitmap);
+ prevTargetBitmap = NULL;
+ DeleteObject(targetBitmap);
+ targetBitmap = NULL;
+ }
+
+ SelectFont(targetDC, prevTargetFont);
+ SelectBitmap(sourceDC, prevSourceBitmap);
+ }
+
+ if (NULL != sourceDC)
+ DeleteDC(sourceDC);
+
+ if (NULL != targetDC && NULL == targetBitmap)
+ {
+ DeleteDC(targetDC);
+ targetDC = NULL;
+ prevTargetBitmap = NULL;
+ }
+
+ if (NULL != backBitmap)
+ DeleteObject(backBitmap);
+
+ *prevBitmapOut = prevTargetBitmap;
+ if (NULL != widthOut)
+ *widthOut = width;
+ if (NULL != heightOut)
+ *heightOut = height;
+
+ return targetDC;
+}
+
+static void
+WelcomeWidget_ResetContext(WelcomeWidget * self)
+{
+ if (NULL == self ||
+ NULL == self->context)
+ {
+ return;
+ }
+
+ if (NULL != self->previousBitmap)
+ {
+ HBITMAP bitmap = SelectBitmap(self->context, self->previousBitmap);
+ if (NULL != bitmap)
+ DeleteObject(bitmap);
+ }
+
+ DeleteDC(self->context);
+ self->context = NULL;
+ self->previousBitmap = NULL;
+ self->contextWidth = 0;
+ self->contextHeight = 0;
+}
+
+static BOOL
+WelcomeWidget_GetViewOrigin(HWND hwnd, POINT *pt)
+{
+ SCROLLINFO scrollInfo;
+
+ if (NULL == pt)
+ return FALSE;
+
+ scrollInfo.cbSize = sizeof(scrollInfo);
+ scrollInfo.fMask = SIF_POS;
+
+ if (FALSE == GetScrollInfo(hwnd, SB_HORZ, &scrollInfo))
+ return FALSE;
+ pt->x = -scrollInfo.nPos;
+
+ if (FALSE == GetScrollInfo(hwnd, SB_VERT, &scrollInfo))
+ return FALSE;
+ pt->y = -scrollInfo.nPos;
+
+ return TRUE;
+}
+
+static BOOL
+WelcomeWidget_UpdateHelpPosition(WelcomeWidget *self, HWND hwnd, const RECT *clientRect, BOOL redraw)
+{
+ HWND elementWindow;
+ RECT elementRect;
+ unsigned int positionFlags;
+ long left, top;
+ POINT origin;
+
+ if (NULL == self || NULL == hwnd || NULL == clientRect)
+ return FALSE;
+
+ elementWindow = GetDlgItem(hwnd, WELCOMEWIDGET_HELP_LINK);
+ if (NULL == elementWindow)
+ return FALSE;
+
+
+ if (FALSE == WelcomeWidget_GetViewOrigin(hwnd, &origin))
+ {
+ origin.x = 0;
+ origin.y = 0;
+ }
+
+ if (FALSE == GetWindowRect(elementWindow, &elementRect))
+ return FALSE;
+
+ positionFlags = SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE;
+ if (FALSE == redraw)
+ positionFlags |= SWP_NOREDRAW;
+
+ left = clientRect->right;
+ left -= (RECTWIDTH(elementRect) + 2);
+ if (left < clientRect->left)
+ left = clientRect->left;
+
+ top = clientRect->top;
+ top += 2;
+
+ return SetWindowPos(elementWindow, NULL, left, origin.y + top, 0, 0, positionFlags);
+}
+
+static BOOL
+WelcomeWidget_EnsureHelpVisible(WelcomeWidget *self, HWND hwnd)
+{
+ HWND elementWindow;
+ POINT origin;
+
+ if (NULL == self || NULL == hwnd)
+ return FALSE;
+
+ elementWindow = GetDlgItem(hwnd, WELCOMEWIDGET_HELP_LINK);
+ if (NULL == elementWindow)
+ return FALSE;
+
+ if (FALSE != WelcomeWidget_GetViewOrigin(hwnd, &origin) &&
+ 0 != origin.y)
+ {
+ return WIDGET_SCROLL(hwnd, 0, origin.y, TRUE);
+ }
+
+ return FALSE;
+}
+
+static BOOL
+WelcomeWidget_GetHelpUrl(wchar_t *buffer, size_t bufferMax)
+{
+ if (NULL == buffer)
+ return FALSE;
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_WELCOMEWIDGET_HELP_URL, buffer, bufferMax);
+ return (L'\0' != buffer[0]);
+}
+
+static BOOL
+WelcomeWidget_InitCb(HWND hwnd, void **object, void *param)
+{
+ wchar_t buffer[64] = {0};
+ WelcomeWidget *self = (WelcomeWidget*)malloc(sizeof(WelcomeWidget));
+
+ if (NULL == self)
+ return FALSE;
+
+ ZeroMemory(self, sizeof(WelcomeWidget));
+ WIDGET_ENABLE_CHILDREN_SCROLL(hwnd, FALSE);
+
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_HELP_LINK, buffer, ARRAYSIZE(buffer));
+ CommandLink_CreateWindow(WS_EX_NOPARENTNOTIFY,
+ buffer,
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP |
+ /*CLS_ALWAYSUNDERLINE |*/ CLS_HOTTRACK | CLS_HIGHLIGHTCOLOR,
+ 0, 0, 0, 0, hwnd, WELCOMEWIDGET_HELP_LINK);
+
+ *object = self;
+
+ return TRUE;
+}
+
+static void
+WelcomeWidget_DestroyCb(WelcomeWidget *self, HWND hwnd)
+{
+ if (NULL == self)
+ return;
+
+ WelcomeWidget_ResetContext(self);
+ if (NULL != self->helpFont)
+ DeleteObject(self->helpFont);
+
+ free(self);
+}
+
+
+static void
+WelcomeWidget_LayoutCb(WelcomeWidget *self, HWND hwnd, WidgetStyle *style,
+ const RECT *clientRect, SIZE *viewSize, BOOL redraw)
+{
+ if (NULL == self || NULL == style)
+ return;
+
+ if (NULL == self->context)
+ {
+ self->context = WelcomeWidget_CreateContext(hwnd, style, &self->previousBitmap, &self->contextWidth, &self->contextHeight);
+ if (NULL == self->context)
+ return;
+ }
+
+ viewSize->cx = self->contextWidth +
+ WIDGETSTYLE_DLU_TO_HORZ_PX(style, WELCOMEWIDGET_OFFSET_LEFT_DLU);
+
+ viewSize->cy = self->contextHeight +
+ WIDGETSTYLE_DLU_TO_VERT_PX(style, WELCOMEWIDGET_OFFSET_TOP_DLU);
+
+
+ WelcomeWidget_UpdateHelpPosition(self, hwnd, clientRect, redraw);
+}
+
+
+static BOOL
+WelcomeWidget_PaintCb(WelcomeWidget *self, HWND hwnd, WidgetStyle *style, HDC hdc, const RECT *paintRect, BOOL erase)
+{
+ FillRegion fillRegion;
+
+ FillRegion_Init(&fillRegion, paintRect);
+
+ if (NULL != self->context)
+ {
+ RECT partRect, rect;
+ long offsetX, offsetY;
+
+ GetClientRect(hwnd, &rect);
+
+ SetRect(&partRect, 0, 0, self->contextWidth, self->contextHeight);
+
+ offsetX = rect.left + WIDGETSTYLE_DLU_TO_HORZ_PX(style, WELCOMEWIDGET_OFFSET_LEFT_DLU);
+ offsetY = WIDGETSTYLE_DLU_TO_HORZ_PX(style, WELCOMEWIDGET_OFFSET_RIGHT_DLU);
+ if ((offsetX + partRect.right + offsetY) < rect.right)
+ offsetX += (rect.right - (offsetX + partRect.right + offsetY))/2;
+
+ offsetY = rect.top + WIDGETSTYLE_DLU_TO_VERT_PX(style, WELCOMEWIDGET_OFFSET_TOP_DLU);
+
+ OffsetRect(&partRect, offsetX, offsetY);
+
+ if (FALSE != IntersectRect(&rect, &partRect, paintRect) &&
+ FALSE != BitBlt(hdc, rect.left, rect.top, RECTWIDTH(rect), RECTHEIGHT(rect),
+ self->context, rect.left - partRect.left, rect.top - partRect.top, SRCCOPY))
+ {
+ FillRegion_ExcludeRect(&fillRegion, &rect);
+ }
+
+ }
+
+
+ if (FALSE != erase)
+ FillRegion_BrushFill(&fillRegion, hdc, WIDGETSTYLE_BACK_BRUSH(style));
+
+ FillRegion_Uninit(&fillRegion);
+
+ return TRUE;
+}
+
+static void
+WelcomeWidget_StyleColorChangedCb(WelcomeWidget *self, HWND hwnd, WidgetStyle *style)
+{
+ HWND elementWindow;
+
+ if (NULL == self)
+ return;
+
+ WelcomeWidget_ResetContext(self);
+ self->context = WelcomeWidget_CreateContext(hwnd, style, &self->previousBitmap, &self->contextWidth, &self->contextHeight);
+
+ elementWindow = GetDlgItem(hwnd, WELCOMEWIDGET_HELP_LINK);
+ if (NULL != elementWindow)
+ {
+ CommandLink_SetBackColor(elementWindow, WIDGETSTYLE_BACK_COLOR(style));
+ CommandLink_SetTextColor(elementWindow, WelcomeWidget_GetTextColor(style, NULL, TRUE));
+ CommandLink_SetHighlightColor(elementWindow, WelcomeWidget_GetTextColor(style, NULL, FALSE));
+ }
+}
+
+static void
+WelcomeWidget_StyleFontChangedCb(WelcomeWidget *self, HWND hwnd, WidgetStyle *style)
+{
+ HWND elementWindow;
+
+ if (NULL == self)
+ return;
+
+ WelcomeWidget_ResetContext(self);
+ self->context = WelcomeWidget_CreateContext(hwnd, style, &self->previousBitmap, &self->contextWidth, &self->contextHeight);
+
+ elementWindow = GetDlgItem(hwnd, WELCOMEWIDGET_HELP_LINK);
+ if (NULL != elementWindow)
+ {
+ SIZE elementSize;
+
+ if (NULL != self->helpFont)
+ DeleteObject(self->helpFont);
+
+ self->helpFont = Graphics_DuplicateFont(WIDGETSTYLE_TEXT_FONT(style), 0, TRUE, TRUE);
+
+ SendMessage(elementWindow, WM_SETFONT, (WPARAM)self->helpFont, 0L);
+ if (FALSE != CommandLink_GetIdealSize(elementWindow, &elementSize))
+ {
+ SetWindowPos(elementWindow, NULL, 0, 0, elementSize.cx, elementSize.cy,
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE);
+ }
+ }
+}
+
+static void
+WelcomeWidget_ScrollCb(WelcomeWidget *self, HWND hwnd, int *dx, int *dy)
+{
+ RECT clientRect;
+ if (FALSE != GetClientRect(hwnd, &clientRect))
+ WelcomeWidget_UpdateHelpPosition(self, hwnd, &clientRect, TRUE);
+
+}
+
+static void
+WelcomeWidget_HelpLinkCb(WelcomeWidget *self, HWND hwnd, NMHDR *pnmh, LRESULT *result)
+{
+ switch(pnmh->code)
+ {
+ case NM_CLICK:
+ {
+ wchar_t buffer[4096] = {0};
+ if (FALSE != WelcomeWidget_GetHelpUrl(buffer, ARRAYSIZE(buffer)))
+ MediaLibrary_ShowHelp(Plugin_GetLibraryWindow(), buffer);
+ }
+ break;
+ case NM_SETFOCUS:
+ WelcomeWidget_EnsureHelpVisible(self, hwnd);
+ break;
+
+ }
+}
+
+static BOOL
+WelcomeWidget_NotifyCb(WelcomeWidget *self, HWND hwnd, NMHDR *pnmh, LRESULT *result)
+{
+ switch(pnmh->idFrom)
+ {
+ case WELCOMEWIDGET_HELP_LINK:
+ WelcomeWidget_HelpLinkCb(self, hwnd, pnmh, result);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL
+WelcomeWidget_HelpCb(WelcomeWidget *self, HWND hwnd, wchar_t *buffer, size_t bufferMax)
+{
+ return WelcomeWidget_GetHelpUrl(buffer, bufferMax);
+}
+
+HWND WelcomeWidget_CreateWindow(HWND parentWindow, int x, int y, int width, int height, BOOL border, unsigned int controlId)
+{
+ const static WidgetInterface welcomeWidgetInterface =
+ {
+ (WidgetInitCallback)WelcomeWidget_InitCb,
+ (WidgetDestroyCallback)WelcomeWidget_DestroyCb,
+ (WidgetLayoutCallback)WelcomeWidget_LayoutCb,
+ (WidgetPaintCallback)WelcomeWidget_PaintCb,
+ (WidgetStyleCallback)WelcomeWidget_StyleColorChangedCb,
+ (WidgetStyleCallback)WelcomeWidget_StyleFontChangedCb,
+ NULL /*mouseMove*/,
+ NULL /*leftButtonDown*/,
+ NULL /*leftButtonUp*/,
+ NULL /*leftButtonDblClk*/,
+ NULL /*rightButtonDown*/,
+ NULL /*rightButtonUp*/,
+ NULL /*keyDown*/,
+ NULL /*keyUp*/,
+ NULL /*character*/,
+ NULL /*inputRequest*/,
+ NULL /*focusChanged*/,
+ NULL /*contextMenu*/,
+ NULL /*zoomChanging*/,
+ NULL /*scrollBefore*/,
+ (WidgetScrollCallback)WelcomeWidget_ScrollCb,
+ (WidgetNotifyCallback)WelcomeWidget_NotifyCb,
+ (WidgetHelpCallback)WelcomeWidget_HelpCb,
+
+ };
+
+ return Widget_CreateWindow(WIDGET_TYPE_WELCOME,
+ &welcomeWidgetInterface,
+ NULL,
+ ((FALSE != border) ? WS_EX_CLIENTEDGE : 0) | WS_EX_CONTROLPARENT,
+ WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
+ x, y, width, height,
+ parentWindow,
+ controlId, 0L);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/welcomeWidget.h b/Src/Plugins/Library/ml_devices/welcomeWidget.h
new file mode 100644
index 00000000..1a5e7819
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/welcomeWidget.h
@@ -0,0 +1,20 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_WELCOME_WIDGET_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_WELCOME_WIDGET_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+
+
+HWND WelcomeWidget_CreateWindow(HWND parentWindow,
+ int x,
+ int y,
+ int width,
+ int height,
+ BOOL border,
+ unsigned int controlId);
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_WELCOME_WIDGET_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/widget.cpp b/Src/Plugins/Library/ml_devices/widget.cpp
new file mode 100644
index 00000000..c5e99c6e
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/widget.cpp
@@ -0,0 +1,1284 @@
+#include "main.h"
+#include "./widget.h"
+
+typedef
+enum WidgetState
+{
+ WIDGET_STATE_MOUSE_MOVE_TRACKED = (1 << 0),
+ WIDGET_STATE_DISABLE_CHILDREN_SCROLL = (1 << 1),
+} WidgetState;
+
+DEFINE_ENUM_FLAG_OPERATORS(WidgetState);
+
+#define WIDGET_IS_FROZEN(_widget) (0 != (_widget)->freezer)
+
+#define WIDGET_IS_MOUSE_MOVE_TRACKED(_widget) (0 != (WIDGET_STATE_MOUSE_MOVE_TRACKED & (_widget)->state))
+#define WIDGET_SET_MOUSE_MOVE_TRACK(_widget) (((_widget)->state) |= WIDGET_STATE_MOUSE_MOVE_TRACKED)
+#define WIDGET_UNSET_MOUSE_MOVE_TRACK(_widget) (((_widget)->state) &= ~WIDGET_STATE_MOUSE_MOVE_TRACKED)
+
+
+#define WIDGET_IS_CHILDREN_SCROLL_DISABLED(_widget) (0 != (WIDGET_STATE_DISABLE_CHILDREN_SCROLL & (_widget)->state))
+#define WIDGET_SET_DISABLE_CHILDREN_SCROLL(_widget) (((_widget)->state) |= WIDGET_STATE_DISABLE_CHILDREN_SCROLL)
+#define WIDGET_UNSET_DISABLE_CHILDREN_SCROLL(_widget) (((_widget)->state) &= ~WIDGET_STATE_DISABLE_CHILDREN_SCROLL)
+
+typedef struct Widget
+{
+ unsigned int type;
+ WidgetState state;
+ const WidgetInterface *callbacks;
+ void *object;
+ WidgetStyle *style;
+ wchar_t *text;
+ HFONT font;
+ SIZE viewSize;
+ POINT viewOrigin;
+ int wheelCarryover;
+ size_t freezer;
+} Widget;
+
+typedef struct WidgetCreateParam
+{
+ unsigned int type;
+ const WidgetInterface *callbacks;
+ void *param;
+ const wchar_t *text;
+} WidgetCreateParam;
+
+#define WIDGET(_hwnd) ((Widget*)(LONGX86)GetWindowLongPtrW((_hwnd), 0))
+#define WIDGET_RET_VOID(_view, _hwnd) {(_view) = WIDGET((_hwnd)); if (NULL == (_view)) return;}
+#define WIDGET_RET_VAL(_view, _hwnd, _error) {(_view) = WIDGET((_hwnd)); if (NULL == (_view)) return (_error);}
+
+#define WIDGETSTYLE(_widget) (((Widget*)(_widget))->style)
+#define WIDGETOBJECT(_widget) (((Widget*)(_widget))->object)
+#define WIDGETCALLBACKS(_widget) (((Widget*)(_widget))->callbacks)
+
+static UINT WINAMP_WM_DIRECT_MOUSE_WHEEL = WM_NULL;
+
+static LRESULT CALLBACK
+Widget_WindowProc(HWND hwnd, unsigned int uMsg, WPARAM wParam, LPARAM lParam);
+
+
+static ATOM
+Widget_GetClassAtom(HINSTANCE instance)
+{
+ WNDCLASSEXW klass;
+ ATOM klassAtom;
+
+ klassAtom = (ATOM)GetClassInfoExW(instance, WIDGET_WINDOW_CLASS, &klass);
+ if (0 != klassAtom)
+ return klassAtom;
+
+ memset(&klass, 0, sizeof(klass));
+ klass.cbSize = sizeof(klass);
+ klass.style = CS_DBLCLKS;
+ klass.lpfnWndProc = Widget_WindowProc;
+ klass.cbClsExtra = 0;
+ klass.cbWndExtra = sizeof(Widget*);
+ klass.hInstance = instance;
+ klass.hIcon = NULL;
+ klass.hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
+ klass.hbrBackground = NULL;
+ klass.lpszMenuName = NULL;
+ klass.lpszClassName = WIDGET_WINDOW_CLASS;
+ klass.hIconSm = NULL;
+ klassAtom = RegisterClassExW(&klass);
+
+ return klassAtom;
+}
+
+HWND
+Widget_CreateWindow(unsigned int type, const WidgetInterface *callbacks,
+ const wchar_t *text, unsigned long windowExStyle, unsigned long windowStyle,
+ int x, int y, int width, int height,
+ HWND parentWindow, unsigned int controlId, void *param)
+{
+ HINSTANCE instance;
+ ATOM klassAtom;
+ HWND hwnd;
+ WidgetCreateParam createParam;
+
+ if (NULL == parentWindow || FALSE == IsWindow(parentWindow))
+ return NULL;
+
+ instance = GetModuleHandleW(NULL);
+ klassAtom = Widget_GetClassAtom(instance);
+ if (0 == klassAtom)
+ return NULL;
+
+ createParam.type = type;
+ createParam.param = param;
+ createParam.callbacks = callbacks;
+ createParam.text = text;
+
+ hwnd = CreateWindowExW(WS_EX_NOPARENTNOTIFY | windowExStyle, (LPCWSTR)MAKEINTATOM(klassAtom), NULL,
+ WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | windowStyle,
+ x, y, width, height,
+ parentWindow, (HMENU)controlId, instance, &createParam);
+
+ return hwnd;
+}
+
+
+static LRESULT
+Widget_DefWindowProc(HWND hwnd, unsigned int uMsg, WPARAM wParam, LPARAM lParam)
+{
+ Widget *self;
+
+ self = WIDGET(hwnd);
+
+ if (NULL != self && NULL != self->callbacks->messageProc)
+ {
+ LRESULT result;
+ result = 0;
+ if (FALSE != self->callbacks->messageProc(self->object,
+ hwnd, uMsg, wParam, lParam, &result))
+ {
+ return result;
+ }
+ }
+
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+}
+
+static void
+Widget_Freeze(Widget *self)
+{
+ if (NULL != self)
+ self->freezer++;
+}
+
+static void
+Widget_Thaw(Widget *self)
+{
+ if (NULL != self && 0 != self->freezer)
+ self->freezer--;
+}
+
+static INT
+Widget_ScrollBarOffsetPos(HWND hwnd, INT barType, INT delta, BOOL redraw)
+{
+ Widget *self;
+ INT position;
+ SCROLLINFO scrollInfo;
+
+ self = WIDGET(hwnd);
+
+ scrollInfo.cbSize = sizeof(scrollInfo);
+ scrollInfo.fMask = SIF_POS | SIF_RANGE | SIF_PAGE;
+
+ if (FALSE == GetScrollInfo(hwnd, barType, &scrollInfo))
+ return 0;
+
+ position = scrollInfo.nPos + delta;
+
+ if (position < scrollInfo.nMin)
+ position = scrollInfo.nMin;
+ else if (position > (scrollInfo.nMax - (INT)scrollInfo.nPage))
+ position = scrollInfo.nMax - (INT)scrollInfo.nPage + 1;
+
+ delta = position - scrollInfo.nPos;
+
+ scrollInfo.fMask = SIF_POS;
+ scrollInfo.nPos = position;
+ SetScrollInfo(hwnd, barType, &scrollInfo, redraw);
+
+ if (NULL != self)
+ {
+ if (SB_HORZ == barType)
+ self->viewOrigin.x = -position;
+ else
+ self->viewOrigin.y = -position;
+ }
+
+ return delta;
+}
+
+static BOOL
+Widget_ScrollContent(HWND hwnd, int dx, int dy, BOOL redraw)
+{
+ Widget *self;
+ UINT scrollFlags;
+ HRGN invalidRgn;
+ INT scrollError;
+
+ if (0 == dx && 0 == dy)
+ return FALSE;
+
+ self = WIDGET(hwnd);
+ if (NULL != self &&
+ NULL != self->callbacks &&
+ NULL != self->callbacks->scrollBefore)
+ {
+ self->callbacks->scrollBefore(WIDGETOBJECT(self), hwnd, &dx, &dy);
+ if (0 == dx && 0 == dy)
+ return FALSE;
+ }
+
+
+ scrollFlags = (FALSE == WIDGET_IS_CHILDREN_SCROLL_DISABLED(self)) ? SW_SCROLLCHILDREN : 0;
+ if (FALSE != redraw)
+ {
+ invalidRgn = CreateRectRgn(0, 0, 0, 0);
+ scrollFlags |= SW_INVALIDATE | SW_ERASE;
+ }
+ else
+ {
+ invalidRgn = NULL;
+ }
+
+ scrollError = ScrollWindowEx(hwnd, -dx, -dy, NULL, NULL, invalidRgn, NULL, scrollFlags);
+ if (ERROR != scrollError)
+ {
+ if (NULL != self &&
+ NULL != self->callbacks &&
+ NULL != self->callbacks->scroll)
+ {
+ self->callbacks->scroll(WIDGETOBJECT(self), hwnd, &dx, &dy);
+ }
+
+ if (FALSE != redraw && NULLREGION != scrollError)
+ {
+ RedrawWindow(hwnd, NULL, invalidRgn,
+ RDW_ERASENOW | RDW_UPDATENOW | RDW_ALLCHILDREN);
+ }
+ }
+
+ if (NULL != invalidRgn)
+ DeleteObject(invalidRgn);
+
+ return (ERROR != scrollError);
+}
+
+static BOOL
+Widget_SyncContentOrigin(HWND hwnd, BOOL redraw)
+{
+ Widget *self;
+ RECT clientRect;
+ SCROLLINFO scrollInfo;
+ INT dx, dy;
+
+ WIDGET_RET_VAL(self, hwnd, FALSE);
+
+
+ scrollInfo.cbSize = sizeof(scrollInfo);
+ scrollInfo.fMask = SIF_POS;
+
+ if (FALSE == GetClientRect(hwnd, &clientRect))
+ SetRectEmpty(&clientRect);
+
+ if (self->viewSize.cx < RECTWIDTH(clientRect))
+ {
+ scrollInfo.nPos = 0;
+ dx = scrollInfo.nPos + self->viewOrigin.x;
+ self->viewOrigin.x = 0;
+ }
+ else if (FALSE != GetScrollInfo(hwnd, SB_HORZ, &scrollInfo))
+ {
+ dx = scrollInfo.nPos + self->viewOrigin.x;
+ self->viewOrigin.x = -scrollInfo.nPos;
+ }
+ else
+ dx = 0;
+
+ if (FALSE != GetScrollInfo(hwnd, SB_VERT, &scrollInfo))
+ {
+ dy = scrollInfo.nPos + self->viewOrigin.y;
+ self->viewOrigin.y = -scrollInfo.nPos;
+ }
+ else
+ dy = 0;
+
+ if (0 == dx && 0 == dy)
+ return FALSE;
+
+ return Widget_ScrollContent(hwnd, dx, dy, redraw);
+}
+
+
+static BOOL
+Widget_ScrollWindow(HWND hwnd, INT dx, INT dy, BOOL redraw)
+{
+ if (0 != dx)
+ dx = Widget_ScrollBarOffsetPos(hwnd, SB_HORZ, dx, redraw);
+
+ if (0 != dy)
+ dy = Widget_ScrollBarOffsetPos(hwnd, SB_VERT, dy, redraw);
+
+ return Widget_ScrollContent(hwnd, dx, dy, redraw);
+}
+
+static BOOL
+Widget_ScrollBarAction(HWND hwnd, INT barType, INT actionLayout, INT line, BOOL redraw)
+{
+ INT delta;
+ SCROLLINFO scrollInfo;
+
+ scrollInfo.cbSize = sizeof(scrollInfo);
+ scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
+
+ if (FALSE == GetScrollInfo(hwnd, barType, &scrollInfo))
+ return FALSE;
+
+ switch(actionLayout)
+ {
+ case SB_BOTTOM:
+ delta = scrollInfo.nMax - scrollInfo.nPos;
+ break;
+ case SB_TOP:
+ delta = scrollInfo.nMin - scrollInfo.nPos;
+ break;
+ case SB_LINEDOWN:
+ delta = line;
+ break;
+ case SB_LINEUP:
+ delta = -line;
+ break;
+ case SB_PAGEDOWN:
+ delta = (INT)scrollInfo.nPage;
+ break;
+ case SB_PAGEUP:
+ delta = -(INT)scrollInfo.nPage;
+ break;
+ case SB_THUMBTRACK:
+ delta = scrollInfo.nTrackPos - scrollInfo.nPos;
+ break;
+ case SB_THUMBPOSITION:
+ case SB_ENDSCROLL:
+ delta = 0;
+ break;
+ default:
+ return FALSE;
+ }
+
+ if(0 != delta)
+ {
+ Widget_ScrollWindow(hwnd,
+ (SB_HORZ == barType) ? delta : 0,
+ (SB_VERT == barType) ? delta : 0,
+ redraw);
+ }
+ else
+ Widget_SyncContentOrigin(hwnd, redraw);
+
+ return TRUE;
+}
+
+static BOOL
+Widget_ScrollBarUpdate(HWND hwnd, INT barType, UINT page, INT max, BOOL redraw)
+{
+ Widget *self;
+ SCROLLINFO scrollInfo;
+ UINT windowStyle, styleFilter;
+
+ WIDGET_RET_VAL(self, hwnd, FALSE);
+
+ windowStyle = GetWindowStyle(hwnd);
+
+ switch(barType)
+ {
+ case SB_HORZ: styleFilter = WS_HSCROLL; break;
+ case SB_VERT: styleFilter = WS_VSCROLL; break;
+ default: return FALSE;
+ }
+
+ scrollInfo.cbSize = sizeof(SCROLLINFO);
+ scrollInfo.fMask = SIF_PAGE | SIF_RANGE;
+
+ if (page >= (UINT)max)
+ {
+ if (0 == (styleFilter & windowStyle))
+ return FALSE;
+
+ scrollInfo.nPage = page + 1;
+ scrollInfo.nMin = 0;
+ scrollInfo.nMax = max;
+ scrollInfo.nPos = scrollInfo.nMin;
+ scrollInfo.nTrackPos = scrollInfo.nPos;
+ scrollInfo.fMask |= (SIF_POS | SIF_TRACKPOS);
+
+ Widget_Freeze(self);
+ SetScrollInfo(hwnd, barType, &scrollInfo, redraw);
+ Widget_Thaw(self);
+
+ windowStyle = GetWindowStyle(hwnd);
+ if (0 != (styleFilter & windowStyle))
+ SetWindowStyle(hwnd, windowStyle & ~styleFilter);
+
+ return TRUE;
+ }
+
+
+
+ if (FALSE == GetScrollInfo(hwnd, barType, &scrollInfo))
+ {
+ if (ERROR_NO_SCROLLBARS == GetLastError())
+ {
+ scrollInfo.nPage = 0;
+ scrollInfo.nMax = 0;
+ scrollInfo.nMin = 0;
+ scrollInfo.nPos = scrollInfo.nMin;
+ scrollInfo.nTrackPos = scrollInfo.nPos;
+ }
+ else
+ return FALSE;
+ }
+
+ scrollInfo.fMask = 0;
+
+ if (scrollInfo.nPage != page)
+ {
+ scrollInfo.nPage = page;
+ scrollInfo.fMask |= SIF_PAGE;
+ }
+
+ if (scrollInfo.nMax != max)
+ {
+ scrollInfo.nMax = max;
+ scrollInfo.fMask |= SIF_RANGE;
+ }
+
+ if (0 == (styleFilter & windowStyle))
+ {
+ scrollInfo.fMask |= (SIF_POS | SIF_TRACKPOS);
+ scrollInfo.nPos = scrollInfo.nMin;
+ scrollInfo.nTrackPos = scrollInfo.nMin;
+ }
+
+ if (0 == scrollInfo.fMask)
+ return FALSE;
+
+ Widget_Freeze(self);
+ SetScrollInfo(hwnd, barType, &scrollInfo, redraw);
+ Widget_Thaw(self);
+
+
+ if (0 == (styleFilter & windowStyle))
+ {
+ windowStyle = GetWindowStyle(hwnd);
+ if (0 == (styleFilter & windowStyle))
+ SetWindowStyle(hwnd, windowStyle | styleFilter);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+Widget_Layout(HWND hwnd, BOOL redraw)
+{
+ Widget *self;
+ RECT rect;
+ size_t iteration;
+
+ WIDGET_RET_VOID(self, hwnd);
+
+ iteration = 0;
+ do
+ {
+ if (iteration++ > 2)
+ break;
+
+ if (FALSE == GetClientRect(hwnd, &rect))
+ break;
+
+ SetSize(&self->viewSize, 0, 0);
+
+ if (NULL != self->callbacks->layout)
+ {
+ self->callbacks->layout(self->object, hwnd, self->style, &rect, &self->viewSize, redraw);
+ }
+
+ if (FALSE != IsSizeEmpty(&self->viewSize))
+ SetSize(&self->viewSize, RECTWIDTH(rect), RECTHEIGHT(rect));
+
+ }
+ while(FALSE != Widget_ScrollBarUpdate(hwnd, SB_HORZ, RECTWIDTH(rect), self->viewSize.cx, redraw) ||
+ FALSE != Widget_ScrollBarUpdate(hwnd, SB_VERT, RECTHEIGHT(rect), self->viewSize.cy, redraw));
+
+ Widget_SyncContentOrigin(hwnd, redraw);
+}
+
+static BOOL
+Widget_Paint(HWND hwnd, HDC hdc, const RECT *paintRect, BOOL erase)
+{
+ Widget *self;
+ BOOL result;
+
+ self = WIDGET(hwnd);
+ if (NULL == self || NULL == self->style)
+ return FALSE;
+
+
+ if (NULL != self->callbacks->paint)
+ {
+ POINT prevOrigin;
+ RECT viewRect;
+
+ CopyRect(&viewRect, paintRect);
+ OffsetRect(&viewRect, -self->viewOrigin.x, -self->viewOrigin.y);
+
+ OffsetViewportOrgEx(hdc, self->viewOrigin.x, self->viewOrigin.y, &prevOrigin);
+
+ result = self->callbacks->paint(self->object, hwnd,
+ self->style, hdc, &viewRect, erase);
+
+ SetViewportOrgEx(hdc, prevOrigin.x, prevOrigin.y, NULL);
+ }
+ else
+ result = FALSE;
+
+ if (FALSE == result)
+ {
+ if (FALSE != erase)
+ result = FillRect(hdc, paintRect, WIDGETSTYLE_BACK_BRUSH(self->style));
+ else
+ result = TRUE;
+ }
+
+ return result;
+}
+
+static void
+Widget_FocusChanged(HWND hwnd, HWND focusWindow, BOOL focusReceived)
+{
+ Widget *self;
+ WIDGET_RET_VOID(self, hwnd);
+
+ if (NULL != self->callbacks &&
+ NULL != self->callbacks->focusChanged)
+ {
+ self->callbacks->focusChanged(self->object, hwnd, focusWindow, focusReceived);
+ }
+}
+
+static LRESULT
+Widget_OnCreate(HWND hwnd, CREATESTRUCT *createStruct)
+{
+ Widget *self;
+ WidgetCreateParam *createParam;
+
+ if (NULL == createStruct)
+ return -1;
+
+ createParam = (WidgetCreateParam*)createStruct->lpCreateParams;
+ if (NULL == createParam)
+ return -1;
+
+ self = (Widget*)malloc(sizeof(Widget));
+ if (NULL == self)
+ return -1;
+
+ SetLastError(ERROR_SUCCESS);
+ if (!SetWindowLongPtr(hwnd, 0, (LONGX86)self) && ERROR_SUCCESS != GetLastError())
+ return -1;
+
+ memset(self, 0, sizeof(Widget));
+
+ if (WM_NULL == WINAMP_WM_DIRECT_MOUSE_WHEEL)
+ WINAMP_WM_DIRECT_MOUSE_WHEEL = RegisterWindowMessageW(L"WINAMP_WM_DIRECT_MOUSE_WHEEL");
+
+ self->type = createParam->type;
+ self->callbacks = createParam->callbacks;
+
+ Widget_Freeze(self);
+
+ if (NULL != createParam->text)
+ SendMessage(hwnd, WM_SETTEXT, 0, (LPARAM)createParam->text);
+
+ MLSkinWindow2(Plugin_GetLibraryWindow(), hwnd, SKINNEDWND_TYPE_SCROLLWND,
+ SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
+
+ if (NULL != self->callbacks->init &&
+ FALSE == self->callbacks->init(hwnd, &self->object, createParam->param))
+ {
+ return -1;
+ }
+
+ Widget_Thaw(self);
+
+ return 0;
+}
+
+static void
+Widget_OnDestroy(HWND hwnd)
+{
+ Widget *self;
+
+ self = WIDGET(hwnd);
+ SetWindowLongPtr(hwnd, 0, 0);
+
+ if (NULL == self)
+ return;
+
+ Widget_Freeze(self);
+
+ if (NULL != self->callbacks->destroy)
+ self->callbacks->destroy(self->object, hwnd);
+
+ String_Free(self->text);
+
+ free(self);
+}
+
+static void
+Widget_OnPaint(HWND hwnd)
+{
+ PAINTSTRUCT ps;
+
+ if (NULL != BeginPaint(hwnd, &ps))
+ {
+ if (FALSE == Widget_Paint(hwnd, ps.hdc, &ps.rcPaint, ps.fErase))
+ {
+ COLORREF backColor, prevBackColor;
+
+ backColor = Graphics_GetSkinColor(WADLG_WNDBG);
+ prevBackColor = SetBkColor(ps.hdc, backColor);
+
+ ExtTextOut(ps.hdc, 0, 0, ETO_OPAQUE, &ps.rcPaint, NULL, 0, NULL);
+ SetBkColor(ps.hdc, prevBackColor);
+ }
+ EndPaint(hwnd, &ps);
+ }
+}
+
+static void
+Widget_OnPrintClient(HWND hwnd, HDC hdc, UINT options)
+{
+ RECT clientRect;
+ if (GetClientRect(hwnd, &clientRect))
+ {
+ Widget_Paint(hwnd, hdc, &clientRect, TRUE);
+ }
+}
+
+static void
+Widget_OnWindowPosChanged(HWND hwnd, WINDOWPOS *windowPos)
+{
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE | SWP_FRAMECHANGED) & windowPos->flags))
+ {
+ Widget *self;
+ WIDGET_RET_VOID(self, hwnd);
+
+ if (FALSE != WIDGET_IS_FROZEN(self))
+ return;
+
+ Widget_Layout(hwnd, 0 == (SWP_NOREDRAW & windowPos->flags));
+ }
+}
+
+static LRESULT
+Widget_OnSetText(HWND hwnd, LPCWSTR text)
+{
+ Widget *self;
+ WIDGET_RET_VAL(self, hwnd, FALSE);
+
+ String_Free(self->text);
+
+ if (NULL == text)
+ self->text = NULL;
+ else if (FALSE != IS_INTRESOURCE(text))
+ {
+ WCHAR buffer[4096] = {0};
+ ResourceString_CopyTo(buffer, text, ARRAYSIZE(buffer));
+ self->text = String_Duplicate(buffer);
+ }
+ else
+ self->text = String_Duplicate(text);
+
+ return TRUE;
+}
+
+static LRESULT
+Widget_OnGetText(HWND hwnd, LPWSTR buffer, size_t bufferMax)
+{
+ Widget *self;
+
+ WIDGET_RET_VAL(self, hwnd, 0);
+
+ return String_CopyTo(buffer, self->text, bufferMax);
+}
+
+static LRESULT
+Widget_OnGetTextLength(HWND hwnd)
+{
+ Widget *self;
+ WIDGET_RET_VAL(self, hwnd, 0);
+
+ return ( NULL != self->text) ? lstrlenW(self->text) : 0;
+}
+
+
+static void
+Widget_OnSetFont(HWND hwnd, HFONT font, BOOL redraw)
+{
+ Widget *self;
+
+ WIDGET_RET_VOID(self, hwnd);
+
+ self->font = font;
+
+ if (NULL != redraw)
+ InvalidateRect(hwnd, NULL, TRUE);
+}
+
+static HFONT
+Widget_OnGetFont(HWND hwnd)
+{
+ Widget *self;
+ WIDGET_RET_VAL(self, hwnd, NULL);
+
+ return self->font;
+}
+
+static void
+Widget_OnVertScroll(HWND hwnd, INT actionLayout, INT trackPosition, HWND scrollBar)
+{
+ Widget *self;
+ WIDGET_RET_VOID(self, hwnd);
+
+ Widget_ScrollBarAction(hwnd, SB_VERT, actionLayout, self->style->unitSize.cy, TRUE);
+}
+
+static void
+Widget_OnHorzScroll(HWND hwnd, INT actionLayout, INT trackPosition, HWND scrollBar)
+{
+ Widget *self;
+ WIDGET_RET_VOID(self, hwnd);
+
+ Widget_ScrollBarAction(hwnd, SB_HORZ, actionLayout, self->style->unitSize.cx, TRUE);
+}
+
+static void
+Widget_OnMouseWheel(HWND hwnd, INT virtualKeys, INT distance, LONG pointer_s)
+{
+ Widget *self;
+ UINT wheelScroll;
+ INT scrollLines;
+ UINT windowStyle;
+ INT barType;
+
+ WIDGET_RET_VOID(self, hwnd);
+
+ windowStyle = GetWindowStyle(hwnd);
+
+ if (0 != (WS_VSCROLL & windowStyle))
+ barType = SB_VERT;
+ else if (0 != (WS_HSCROLL & windowStyle))
+ barType = SB_HORZ;
+ else
+ return;
+
+ if (FALSE == SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &wheelScroll, 0))
+ wheelScroll = 3;
+
+ if (0 == wheelScroll)
+ return;
+
+ if (WHEEL_PAGESCROLL == wheelScroll)
+ {
+ RECT clientRect;
+ GetClientRect(hwnd, &clientRect);
+ if (SB_VERT == barType)
+ wheelScroll = RECTHEIGHT(clientRect)/self->style->unitSize.cy;
+ else
+ wheelScroll = RECTWIDTH(clientRect)/self->style->unitSize.cx;
+ }
+
+ distance += self->wheelCarryover;
+ scrollLines = distance * (INT)wheelScroll / WHEEL_DELTA;
+
+ self->wheelCarryover = distance - scrollLines * WHEEL_DELTA / (INT)wheelScroll;
+
+ if (FALSE != Widget_ScrollWindow(hwnd,
+ (SB_HORZ == barType) ? -(scrollLines * self->style->unitSize.cx) : 0,
+ (SB_VERT == barType) ? -(scrollLines * self->style->unitSize.cy) : 0,
+ TRUE))
+ {
+
+ }
+
+}
+
+
+static void
+Widget_OnMouseMove(HWND hwnd, unsigned int vKeys, long cursor_s)
+{
+ Widget *self;
+ WIDGET_RET_VOID(self, hwnd);
+
+ if (NULL != self->callbacks && NULL != self->callbacks->mouseMove)
+ {
+ BOOL processed;
+ POINT cursor;
+ POINTSTOPOINT(cursor, cursor_s);
+
+
+ processed = self->callbacks->mouseMove(self->object, hwnd, vKeys, &cursor);
+
+ if (FALSE == WIDGET_IS_MOUSE_MOVE_TRACKED(self))
+ {
+ TRACKMOUSEEVENT trackMouse;
+ trackMouse.cbSize = sizeof(trackMouse);
+ trackMouse.dwFlags = TME_LEAVE;
+ trackMouse.hwndTrack = hwnd;
+ if (FALSE != TrackMouseEvent(&trackMouse))
+ WIDGET_SET_MOUSE_MOVE_TRACK(self);
+ }
+
+ if (FALSE != processed)
+ return;
+ }
+
+ DefWindowProc(hwnd, WM_MOUSEMOVE, (WPARAM)vKeys, (LPARAM)cursor_s);
+}
+
+static void
+Widget_OnMouseLeave(HWND hwnd)
+{
+ Widget *self;
+ WIDGET_RET_VOID(self, hwnd);
+
+ WIDGET_UNSET_MOUSE_MOVE_TRACK(self);
+
+ if (NULL != self->callbacks && NULL != self->callbacks->mouseMove)
+ {
+ POINT cursor;
+ cursor.x = 0xEFFFFFFF;
+ cursor.y = 0xEFFFFFFF;
+
+ if (FALSE != self->callbacks->mouseMove(self->object, hwnd, 0, &cursor))
+ return;
+ }
+
+ DefWindowProc(hwnd, WM_MOUSELEAVE, 0, 0L);
+
+}
+
+static void
+Widget_OnLeftButtonDown(HWND hwnd, unsigned int vKeys, long cursor_s)
+{
+ Widget *self;
+ WIDGET_RET_VOID(self, hwnd);
+
+ if (NULL != self->callbacks && NULL != self->callbacks->leftButtonDown)
+ {
+ POINT cursor;
+ POINTSTOPOINT(cursor, cursor_s);
+ if (FALSE != self->callbacks->leftButtonDown(self->object, hwnd, vKeys, &cursor))
+ return;
+ }
+
+ DefWindowProc(hwnd, WM_LBUTTONDOWN, (WPARAM)vKeys, (LPARAM)cursor_s);
+
+}
+
+static void
+Widget_OnLeftButtonUp(HWND hwnd, unsigned int vKeys, long cursor_s)
+{
+ Widget *self;
+ WIDGET_RET_VOID(self, hwnd);
+
+ if (NULL != self->callbacks && NULL != self->callbacks->leftButtonUp)
+ {
+ POINT cursor;
+ POINTSTOPOINT(cursor, cursor_s);
+ if (FALSE != self->callbacks->leftButtonUp(self->object, hwnd, vKeys, &cursor))
+ return;
+ }
+
+ DefWindowProc(hwnd, WM_LBUTTONUP, (WPARAM)vKeys, (LPARAM)cursor_s);
+}
+
+static void
+Widget_OnLeftButtonDblClk(HWND hwnd, unsigned int vKeys, long cursor_s)
+{
+ Widget *self;
+ WIDGET_RET_VOID(self, hwnd);
+
+ if (NULL != self->callbacks && NULL != self->callbacks->leftButtonDblClk)
+ {
+ POINT cursor;
+ POINTSTOPOINT(cursor, cursor_s);
+ if (FALSE != self->callbacks->leftButtonDblClk(self->object, hwnd, vKeys, &cursor))
+ return;
+ }
+ DefWindowProc(hwnd, WM_LBUTTONDBLCLK, (WPARAM)vKeys, (LPARAM)cursor_s);
+}
+
+static void
+Widget_OnRightButtonDown(HWND hwnd, unsigned int vKeys, long cursor_s)
+{
+ Widget *self;
+ WIDGET_RET_VOID(self, hwnd);
+
+ if (NULL != self->callbacks && NULL != self->callbacks->rightButtonDown)
+ {
+ POINT cursor;
+ POINTSTOPOINT(cursor, cursor_s);
+ if (FALSE != self->callbacks->rightButtonDown(self->object, hwnd, vKeys, &cursor))
+ return;
+ }
+
+ DefWindowProc(hwnd, WM_RBUTTONDOWN, (WPARAM)vKeys, (LPARAM)cursor_s);
+
+}
+
+static void
+Widget_OnRightButtonUp(HWND hwnd, unsigned int vKeys, long cursor_s)
+{
+ Widget *self;
+ WIDGET_RET_VOID(self, hwnd);
+
+ if (NULL != self->callbacks && NULL != self->callbacks->rightButtonUp)
+ {
+ POINT cursor;
+ POINTSTOPOINT(cursor, cursor_s);
+ if (FALSE != self->callbacks->rightButtonUp(self->object, hwnd, vKeys, &cursor))
+ return;
+ }
+
+ DefWindowProc(hwnd, WM_RBUTTONUP, (WPARAM)vKeys, (LPARAM)cursor_s);
+}
+
+static void
+Widget_OnKeyDown(HWND hwnd, unsigned int vKey, unsigned int flags)
+{
+ Widget *self;
+ WIDGET_RET_VOID(self, hwnd);
+
+ if (NULL != self->callbacks &&
+ NULL != self->callbacks->keyDown &&
+ FALSE != self->callbacks->keyDown(self->object, hwnd, vKey, flags))
+ {
+ return;
+ }
+
+ DefWindowProc(hwnd, WM_KEYDOWN, (WPARAM)vKey, (LPARAM)flags);
+
+}
+
+static void
+Widget_OnKeyUp(HWND hwnd, unsigned int vKey, unsigned int flags)
+{
+ Widget *self;
+ self = WIDGET(hwnd);
+
+ if (NULL != self &&
+ NULL != self->callbacks &&
+ NULL != self->callbacks->keyUp &&
+ FALSE != self->callbacks->keyUp(self->object, hwnd, vKey, flags))
+ {
+ return;
+ }
+
+ DefWindowProc(hwnd, WM_KEYUP, (WPARAM)vKey, (LPARAM)flags);
+}
+
+static void
+Widget_OnChar(HWND hwnd, unsigned int vKey, unsigned int flags)
+{
+ Widget *self;
+ self = WIDGET(hwnd);
+
+ if (NULL != self &&
+ NULL != self->callbacks &&
+ NULL != self->callbacks->character &&
+ FALSE != self->callbacks->character(self->object, hwnd, vKey, flags))
+ {
+ return;
+ }
+
+ DefWindowProc(hwnd, WM_CHAR, (WPARAM)vKey, (LPARAM)flags);
+}
+
+static unsigned int
+Widget_OnGetDlgCode(HWND hwnd, unsigned int vKey, MSG *message)
+{
+ Widget *self;
+ self = WIDGET(hwnd);
+
+ if (NULL != self &&
+ NULL != self->callbacks &&
+ NULL != self->callbacks->inputRequest)
+ {
+ return self->callbacks->inputRequest(self->object, hwnd, vKey, message);
+ }
+
+ return (unsigned int)DefWindowProc(hwnd, WM_GETDLGCODE, (WPARAM)vKey, (LPARAM)message);
+}
+
+
+static void
+Widget_OnSetFocus(HWND hwnd, HWND focusWindow)
+{
+ Widget_FocusChanged(hwnd, focusWindow, TRUE);
+}
+
+static void
+Widget_OnKillFocus(HWND hwnd, HWND focusWindow)
+{
+ Widget_FocusChanged(hwnd, focusWindow, FALSE);
+}
+
+static void
+Widget_OnContextMenu(HWND hwnd, HWND targetWindow, long cursor_s)
+{
+ BOOL processed;
+ Widget *self;
+ WIDGET_RET_VOID(self, hwnd);
+
+ if (NULL != self->callbacks && NULL != self->callbacks->contextMenu)
+ {
+ POINT cursor;
+ POINTSTOPOINT(cursor, cursor_s);
+ processed = self->callbacks->contextMenu(self->object, hwnd, targetWindow, &cursor);
+ }
+ else
+ processed = FALSE;
+
+ if (FALSE == processed)
+ Widget_DefWindowProc(hwnd, WM_CONTEXTMENU, (WPARAM)targetWindow, (LPARAM)cursor_s);
+}
+
+static unsigned int
+Widget_OnGetType(HWND hwnd)
+{
+ Widget *self;
+ WIDGET_RET_VAL(self, hwnd, WIDGET_TYPE_UNKNOWN);
+
+ return self->type;
+}
+
+static void*
+Widget_OnGetSelf(HWND hwnd)
+{
+ Widget *self;
+ WIDGET_RET_VAL(self, hwnd, NULL);
+
+ return self->object;
+}
+
+static BOOL
+Widget_OnSetStyle(HWND hwnd, WidgetStyle *style)
+{
+ Widget *self;
+ BOOL styleChanged;
+
+ WIDGET_RET_VAL(self, hwnd, FALSE);
+
+ styleChanged = (self->style != style);
+
+ self->style = style;
+
+ if (FALSE != styleChanged)
+ {
+ if (NULL != WIDGETCALLBACKS(self))
+ {
+ if (NULL != WIDGETCALLBACKS(self)->styleColorChanged)
+ WIDGETCALLBACKS(self)->styleColorChanged(WIDGETOBJECT(self), hwnd, WIDGETSTYLE(self));
+
+ if (NULL != WIDGETCALLBACKS(self)->styleFontChanged)
+ WIDGETCALLBACKS(self)->styleFontChanged(WIDGETOBJECT(self), hwnd, WIDGETSTYLE(self));
+ }
+ }
+
+ return TRUE;
+}
+
+static WidgetStyle *
+Widget_OnGetStyle(HWND hwnd)
+{
+ Widget *self;
+ WIDGET_RET_VAL(self, hwnd, NULL);
+
+ return self->style;
+}
+
+static void
+Widget_OnStyleColorChanged(HWND hwnd)
+{
+ Widget *self;
+
+ WIDGET_RET_VOID(self, hwnd);
+
+ if (FALSE != WIDGET_IS_FROZEN(self))
+ return;
+
+ if (NULL != WIDGETCALLBACKS(self) &&
+ NULL != WIDGETCALLBACKS(self)->styleColorChanged)
+ {
+ WIDGETCALLBACKS(self)->styleColorChanged(WIDGETOBJECT(self), hwnd, WIDGETSTYLE(self));
+ }
+}
+
+static void
+Widget_OnStyleFontChanged(HWND hwnd)
+{
+ Widget *self;
+
+ WIDGET_RET_VOID(self, hwnd);
+
+ if (FALSE != WIDGET_IS_FROZEN(self))
+ return;
+
+ if (NULL != WIDGETCALLBACKS(self) &&
+ NULL != WIDGETCALLBACKS(self)->styleFontChanged)
+ {
+ WIDGETCALLBACKS(self)->styleFontChanged(WIDGETOBJECT(self), hwnd, WIDGETSTYLE(self));
+ }
+}
+
+static void
+Widget_OnFreeze(HWND hwnd, BOOL freeze)
+{
+ Widget *self;
+
+ WIDGET_RET_VOID(self, hwnd);
+
+ if (FALSE == freeze)
+ Widget_Thaw(self);
+ else
+ Widget_Freeze(self);
+}
+
+static BOOL
+Widget_OnScroll(HWND hwnd, int dx, int dy, BOOL redraw)
+{
+ return Widget_ScrollWindow(hwnd, dx, dy, redraw);
+}
+
+static LRESULT
+Widget_OnSetScrollPos(HWND hwnd, int dx, int dy, BOOL redraw)
+{
+ if (0 != dx)
+ dx = Widget_ScrollBarOffsetPos(hwnd, SB_HORZ, dx, redraw);
+
+ if (0 != dy)
+ dy = Widget_ScrollBarOffsetPos(hwnd, SB_VERT, dy, redraw);
+
+ return (LRESULT)MAKELONG(dx, dy);
+
+}
+
+static void
+Widget_OnZoomSliderPosChanging(HWND hwnd, NMTRBTHUMBPOSCHANGING *sliderInfo)
+{
+ Widget *self;
+ WIDGET_RET_VOID(self, hwnd);
+
+ if (NULL != WIDGETCALLBACKS(self) &&
+ NULL != WIDGETCALLBACKS(self)->zoomChanging)
+ {
+ WIDGETCALLBACKS(self)->zoomChanging(WIDGETOBJECT(self), hwnd, sliderInfo);
+ }
+}
+
+static LRESULT
+Widget_OnNotify(HWND hwnd, NMHDR *notification)
+{
+ Widget *self;
+
+ self = WIDGET(hwnd);
+ if (NULL != self &&
+ NULL != self->callbacks &&
+ NULL != self->callbacks->notify)
+ {
+ LRESULT result;
+ if (FALSE != self->callbacks->notify(WIDGETOBJECT(self), hwnd, notification, &result))
+ return result;
+ }
+
+ return Widget_DefWindowProc(hwnd, WM_NOTIFY,
+ (WPARAM)notification->idFrom, (LPARAM)notification);
+}
+
+static BOOL
+Widget_OnEnableChildrenScroll(HWND hwnd, BOOL enable)
+{
+ Widget *self;
+ BOOL previous;
+
+ WIDGET_RET_VAL(self, hwnd, FALSE);
+
+ previous = (FALSE == WIDGET_IS_CHILDREN_SCROLL_DISABLED(self));
+
+ if (FALSE != enable)
+ WIDGET_UNSET_DISABLE_CHILDREN_SCROLL(self);
+ else
+ WIDGET_SET_DISABLE_CHILDREN_SCROLL(self);
+
+ return previous;
+}
+
+static BOOL
+Widget_OnGetChildrenScrollEnabled(HWND hwnd)
+{
+ Widget *self;
+ WIDGET_RET_VAL(self, hwnd, FALSE);
+
+ return (FALSE == WIDGET_IS_CHILDREN_SCROLL_DISABLED(self));
+
+}
+static LRESULT CALLBACK
+Widget_WindowProc(HWND hwnd, unsigned int uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_CREATE: return Widget_OnCreate(hwnd, (CREATESTRUCT*)lParam);
+ case WM_DESTROY: Widget_OnDestroy(hwnd); return 0;
+ case WM_PAINT: Widget_OnPaint(hwnd); return 0;
+ case WM_PRINTCLIENT: Widget_OnPrintClient(hwnd, (HDC)wParam, (UINT)lParam); return 0;
+ case WM_PRINT: return 0;
+ case WM_ERASEBKGND: return 0;
+ case WM_WINDOWPOSCHANGED: Widget_OnWindowPosChanged(hwnd, (WINDOWPOS*)lParam); return 0;
+ case WM_SIZE: return 0;
+ case WM_MOVE: return 0;
+ case WM_SETTEXT: return Widget_OnSetText(hwnd, (LPCWSTR)lParam);
+ case WM_GETTEXT: return Widget_OnGetText(hwnd, (LPWSTR)lParam, (INT)wParam);
+ case WM_GETTEXTLENGTH: return Widget_OnGetTextLength(hwnd);
+ case WM_SETFONT: Widget_OnSetFont(hwnd, (HFONT)wParam, (BOOL)LOWORD(lParam)); return 0;
+ case WM_GETFONT: return (LRESULT)Widget_OnGetFont(hwnd);
+ case WM_VSCROLL: Widget_OnVertScroll(hwnd, LOWORD(wParam), (short)HIWORD(wParam), (HWND)lParam); return 0;
+ case WM_HSCROLL: Widget_OnHorzScroll(hwnd, LOWORD(wParam), (short)HIWORD(wParam), (HWND)lParam); return 0;
+ case WM_MOUSEWHEEL: Widget_OnMouseWheel(hwnd, LOWORD(wParam), (short)HIWORD(wParam), (LONG)lParam); return 0;
+ case WM_MOUSEMOVE: Widget_OnMouseMove(hwnd, (unsigned int)wParam, (long)lParam); return 0;
+ case WM_MOUSELEAVE: Widget_OnMouseLeave(hwnd); return 0;
+ case WM_LBUTTONDOWN: Widget_OnLeftButtonDown(hwnd, (unsigned int)wParam, (long)lParam); return 0;
+ case WM_LBUTTONUP: Widget_OnLeftButtonUp(hwnd, (unsigned int)wParam, (long)lParam); return 0;
+ case WM_LBUTTONDBLCLK: Widget_OnLeftButtonDblClk(hwnd, (unsigned int)wParam, (long)lParam); return 0;
+ case WM_RBUTTONDOWN: Widget_OnRightButtonDown(hwnd, (unsigned int)wParam, (long)lParam); return 0;
+ case WM_RBUTTONUP: Widget_OnRightButtonUp(hwnd, (unsigned int)wParam, (long)lParam); return 0;
+ case WM_KEYDOWN: Widget_OnKeyDown(hwnd, (unsigned int)wParam, (unsigned int)lParam); return 0;
+ case WM_KEYUP: Widget_OnKeyUp(hwnd, (unsigned int)wParam, (unsigned int)lParam); return 0;
+ case WM_CHAR: Widget_OnChar(hwnd, (unsigned int)wParam, (unsigned int)lParam); return 0;
+ case WM_GETDLGCODE: return Widget_OnGetDlgCode(hwnd, (unsigned int)wParam, (MSG*)lParam);
+ case WM_SETFOCUS: Widget_OnSetFocus(hwnd, (HWND)wParam); return 0;
+ case WM_KILLFOCUS: Widget_OnKillFocus(hwnd, (HWND)wParam); return 0;
+ case WM_CONTEXTMENU: Widget_OnContextMenu(hwnd, (HWND)wParam, (long)lParam); return 0;
+ case WM_NOTIFY: return Widget_OnNotify(hwnd, (NMHDR*)lParam);
+
+ case WIDGET_WM_GET_TYPE: return (LRESULT)Widget_OnGetType(hwnd);
+ case WIDGET_WM_GET_SELF: return (LRESULT)Widget_OnGetSelf(hwnd);
+ case WIDGET_WM_SET_STYLE: return Widget_OnSetStyle(hwnd, (WidgetStyle*)lParam);
+ case WIDGET_WM_GET_STYLE: return (LRESULT)Widget_OnGetStyle(hwnd);
+ case WIDGET_WM_STYLE_COLOR_CHANGED: Widget_OnStyleColorChanged(hwnd); return 0;
+ case WIDGET_WM_STYLE_FONT_CHANGED: Widget_OnStyleFontChanged(hwnd); return 0;
+ case WIDGET_WM_FREEZE: Widget_OnFreeze(hwnd, (BOOL)wParam); return 0;
+ case WIDGET_WM_SET_SCROLL_POS: return Widget_OnSetScrollPos(hwnd, (short)LOWORD(lParam), (short)HIWORD(lParam), (BOOL)wParam);
+ case WIDGET_WM_SCROLL: return Widget_OnScroll(hwnd, (short)LOWORD(lParam), (short)HIWORD(lParam), (BOOL)wParam);
+ case WIDGET_WM_ZOOM_SLIDER_POS_CHANGING: Widget_OnZoomSliderPosChanging(hwnd, (NMTRBTHUMBPOSCHANGING*)lParam); return 0;
+ case WIDGET_WM_ENABLE_CHILDREN_SCROLL: return Widget_OnEnableChildrenScroll(hwnd, (BOOL)lParam);
+ case WIDGET_WM_GET_CHILDREN_SCROLL_ENABLED: return Widget_OnGetChildrenScrollEnabled(hwnd);
+ }
+
+ if (WINAMP_WM_DIRECT_MOUSE_WHEEL == uMsg &&
+ WM_NULL != WINAMP_WM_DIRECT_MOUSE_WHEEL)
+ {
+ Widget_OnMouseWheel(hwnd, LOWORD(wParam), (SHORT)HIWORD(wParam), (LONG)lParam);
+ return TRUE;
+ }
+
+ return Widget_DefWindowProc(hwnd, uMsg, wParam, lParam);
+}
+
diff --git a/Src/Plugins/Library/ml_devices/widget.h b/Src/Plugins/Library/ml_devices/widget.h
new file mode 100644
index 00000000..673dcd5f
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/widget.h
@@ -0,0 +1,129 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_WIDGET_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_WIDGET_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "./widgetStyle.h"
+
+#define WIDGET_WINDOW_CLASS L"NullsoftDevicesWidget"
+
+
+typedef BOOL (*WidgetInitCallback)(HWND /*hwnd*/, void** /*self_out*/, void* /*param*/);
+typedef void (*WidgetDestroyCallback)(void* /*self*/, HWND /*hwnd*/);
+typedef void (*WidgetLayoutCallback)(void* /*self*/, HWND /*hwnd*/, WidgetStyle* /*style*/, const RECT* /*clientRect*/, SIZE* /*viewSize*/, BOOL /*redraw*/);
+typedef BOOL (*WidgetPaintCallback)(void* /*self*/, HWND /*hwnd*/, WidgetStyle* /*style*/, HDC /*hdc*/, const RECT* /*paintRect*/, BOOL /*erase*/);
+typedef BOOL (*WidgetMessageCallback)(void* /*self*/, HWND /*hwnd*/, unsigned int /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, LRESULT* /*result*/);
+typedef void (*WidgetStyleCallback)(void* /*self*/, HWND /*hwnd*/, WidgetStyle* /*style*/);
+typedef BOOL (*WidgetMouseCallback)(void* /*self*/, HWND /*hwnd*/, unsigned int /*vKeys*/, const POINT* /*cursor*/);
+typedef INT (*WidgetInputCallback)(void* /*self*/, HWND /*hwnd*/, unsigned int /*vKey*/, MSG* /*message*/);
+typedef BOOL (*WidgetKeyCallback)(void* /*self*/, HWND /*hwnd*/, unsigned int /*vKey*/, unsigned int /*flags*/);
+typedef void (*WidgetFocusCallback)(void* /*self*/, HWND /*hwnd*/, HWND /*focusHwnd*/, BOOL /*focusReceived*/);
+typedef BOOL (*WidgetMenuCallback)(void* /*self*/, HWND /*hwnd*/, HWND /*targetWindow*/, const POINT* /*cursor*/);
+typedef void (*WidgetZoomCallback)(void* /*self*/, HWND /*hwnd*/, NMTRBTHUMBPOSCHANGING* /*sliderInfo*/);
+typedef void (*WidgetScrollCallback)(void* /*self*/, HWND /*hwnd*/, int* /*dx*/, int* /*dy*/);
+typedef BOOL (*WidgetNotifyCallback)(void* /*self*/, HWND /*hwnd*/, NMHDR* /*notification*/, LRESULT* /*result*/);
+typedef BOOL (*WidgetHelpCallback)(void* /*self*/, HWND /*hwnd*/, wchar_t* /*buffer*/, size_t /*bufferMax*/);
+
+typedef struct WidgetInterface
+{
+ WidgetInitCallback init;
+ WidgetDestroyCallback destroy;
+ WidgetLayoutCallback layout;
+ WidgetPaintCallback paint;
+ WidgetStyleCallback styleColorChanged;
+ WidgetStyleCallback styleFontChanged;
+ WidgetMouseCallback mouseMove;
+ WidgetMouseCallback leftButtonDown;
+ WidgetMouseCallback leftButtonUp;
+ WidgetMouseCallback leftButtonDblClk;
+ WidgetMouseCallback rightButtonDown;
+ WidgetMouseCallback rightButtonUp;
+ WidgetKeyCallback keyDown;
+ WidgetKeyCallback keyUp;
+ WidgetKeyCallback character;
+ WidgetInputCallback inputRequest;
+ WidgetFocusCallback focusChanged;
+ WidgetMenuCallback contextMenu;
+ WidgetZoomCallback zoomChanging;
+ WidgetScrollCallback scrollBefore;
+ WidgetScrollCallback scroll;
+ WidgetNotifyCallback notify;
+ WidgetHelpCallback help;
+ WidgetMessageCallback messageProc;
+} WidgetInterface;
+
+HWND
+Widget_CreateWindow(unsigned int type,
+ const WidgetInterface *callbacks,
+ const wchar_t *text,
+ unsigned long windowExStyle,
+ unsigned long windowStyle,
+ int x,
+ int y,
+ int width,
+ int height,
+ HWND parentWindow,
+ unsigned int controlId,
+ void *param);
+
+
+#define WIDGET_WM_FIRST (WM_USER + 10)
+
+#define WIDGET_WM_GET_TYPE (WIDGET_WM_FIRST + 0)
+#define WIDGET_GET_TYPE(/*HWND*/ _hwnd)\
+ ((unsigned int)SendMessageW((_hwnd), WIDGET_WM_GET_TYPE, 0, 0L))
+
+#define WIDGET_WM_GET_SELF (WIDGET_WM_FIRST + 1)
+#define WIDGET_GET_SELF(/*HWND*/ _hwnd, _type)\
+ ((_type*)SendMessageW((_hwnd), WIDGET_WM_GET_SELF, 0, 0L))
+
+#define WIDGET_WM_SET_STYLE (WIDGET_WM_FIRST + 2)
+#define WIDGET_SET_STYLE(/*HWND*/ _hwnd, /*WidgetStyle* */ _style)\
+ ((BOOL)SendMessageW((_hwnd), WIDGET_WM_SET_STYLE, 0, (LPARAM)(_style)))
+
+#define WIDGET_WM_GET_STYLE (WIDGET_WM_FIRST + 3)
+#define WIDGET_GET_STYLE(/*HWND*/ _hwnd)\
+ ((WidgetStyle*)SendMessageW((_hwnd), WIDGET_WM_GET_STYLE, 0, 0L))
+
+#define WIDGET_WM_STYLE_COLOR_CHANGED (WIDGET_WM_FIRST + 4)
+#define WIDGET_STYLE_COLOR_CHANGED(/*HWND*/ _hwnd)\
+ (SendMessageW((_hwnd), WIDGET_WM_STYLE_COLOR_CHANGED, 0, 0L))
+
+#define WIDGET_WM_STYLE_FONT_CHANGED (WIDGET_WM_FIRST + 5)
+#define WIDGET_STYLE_FONT_CHANGED(/*HWND*/ _hwnd)\
+ (SendMessageW((_hwnd), WIDGET_WM_STYLE_FONT_CHANGED, 0, 0L))
+
+#define WIDGET_WM_FREEZE (WIDGET_WM_FIRST + 6)
+#define WIDGET_FREEZE(/*HWND*/ _hwnd)\
+ (SendMessageW((_hwnd), WIDGET_WM_FREEZE, TRUE, 0L))
+#define WIDGET_THAW(/*HWND*/ _hwnd)\
+ (SendMessageW((_hwnd), WIDGET_WM_FREEZE, FALSE, 0L))
+
+#define WIDGET_WM_SET_SCROLL_POS (WIDGET_WM_FIRST + 7) // just offsets scroll positions wihtout scrolling view. result = (LRESULT)MAKELPARAM(actualDx,actualDy).
+#define WIDGET_SET_SCROLL_POS(/*HWND*/ _hwnd, /*int*/ _dx, /*int*/ _dy, /*BOOL*/ _redraw)\
+ (SendMessageW((_hwnd), WIDGET_WM_SET_SCROLL_POS, (WPARAM)(_redraw), MAKELPARAM((_dx), (_dy))))
+
+#define WIDGET_WM_SCROLL (WIDGET_WM_FIRST + 8)
+#define WIDGET_SCROLL(/*HWND*/ _hwnd, /*int*/ _dx, /*int*/ _dy, /*BOOL*/ _redraw)\
+ ((BOOL)SendMessageW((_hwnd), WIDGET_WM_SCROLL, (WPARAM)(_redraw), MAKELPARAM((_dx), (_dy))))
+
+#define WIDGET_WM_ZOOM_SLIDER_POS_CHANGING (WIDGET_WM_FIRST + 9)
+#define WIDGET_ZOOM_SLIDER_POS_CHANGING(/*HWND*/ _hwnd, /*NMTRBTHUMBPOSCHANGING* */ _sliderInfo)\
+ ((BOOL)SendMessageW((_hwnd), WIDGET_WM_ZOOM_SLIDER_POS_CHANGING, 0, (LPARAM)(_sliderInfo)))
+
+#define WIDGET_WM_ENABLE_CHILDREN_SCROLL (WIDGET_WM_FIRST + 10)
+#define WIDGET_ENABLE_CHILDREN_SCROLL(/*HWND*/ _hwnd, /*BOOL*/ _enable)\
+ ((BOOL)SendMessageW((_hwnd), WIDGET_WM_ENABLE_CHILDREN_SCROLL, 0, (LPARAM)(_enable)))
+
+#define WIDGET_WM_GET_CHILDREN_SCROLL_ENABLED (WIDGET_WM_FIRST + 11)
+#define WIDGET_GET_CHILDREN_SCROLL_ENABLED(/*HWND*/ _hwnd)\
+ ((BOOL)SendMessageW((_hwnd), WIDGET_WM_GET_CHILDREN_SCROLL_ENABLED, 0, 0L))
+
+#define WIDGET_WM_GET_HELP_URL (WIDGET_WM_FIRST + 12)
+#define WIDGET_GET_HELP_URL(/*HWND*/ _hwnd, /* wchar_t* */ _buffer, /*size_t*/ _bufferMax)\
+ ((BOOL)SendMessageW((_hwnd), WIDGET_WM_GET_HELP_URL, (WPARAM)(_bufferMax), (LPARAM)(_buffer)))
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_WIDGET_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/widgetHost.cpp b/Src/Plugins/Library/ml_devices/widgetHost.cpp
new file mode 100644
index 00000000..0b8e2ae9
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/widgetHost.cpp
@@ -0,0 +1,391 @@
+#include "main.h"
+#include "./widgetHost.h"
+
+
+#define WIDGETHOST_WINDOW_CLASS L"NullsoftDevicesWidgetHost"
+#define WIDGETHOST_WIDGET_ID 1000
+
+typedef
+enum WidgetHostState
+{
+ WIDGETHOST_STATE_FROZEN_UI = (1 << 0),
+} WidgetHostState;
+DEFINE_ENUM_FLAG_OPERATORS(WidgetHostState);
+
+typedef struct WidgetHost
+{
+ WidgetHostState state;
+ WidgetStyle widgetStyle;
+ HFONT font;
+ HRGN updateRegion;
+ POINT updateOffset;
+} WidgetHost;
+
+typedef struct WidgetHostCreateParam
+{
+ WidgetCreateProc widgetCreate;
+ void *widgetCreateParam;
+} WidgetHostCreateParam;
+
+#define WIDGETHOST(_hwnd) ((WidgetHost*)(LONGX86)GetWindowLongPtrW((_hwnd), 0))
+#define WIDGETHOST_RET_VOID(_self, _hwnd) {(_self) = WIDGETHOST((_hwnd)); if (NULL == (_self)) return;}
+#define WIDGETHOST_RET_VAL(_self, _hwnd, _error) {(_self) = WIDGETHOST((_hwnd)); if (NULL == (_self)) return (_error);}
+
+#define WIDGETHOST_WIDGET(_hostWindow) (GetDlgItem((_hostWindow), WIDGETHOST_WIDGET_ID))
+
+#define WIDGETHOST_IS_FROZEN(_self) (0 != (WIDGETHOST_STATE_FROZEN_UI & (_self)->state))
+#define WIDGETHOST_FREEZE(_self) (((_self)->state) |= WIDGETHOST_STATE_FROZEN_UI)
+#define WIDGETHOST_THAW(_self) (((_self)->state) &= ~WIDGETHOST_STATE_FROZEN_UI)
+
+
+static LRESULT CALLBACK
+WidgetHost_WindowProc(HWND hwnd, unsigned int uMsg, WPARAM wParam, LPARAM lParam);
+
+static ATOM
+WidgetHost_GetClassAtom(HINSTANCE instance)
+{
+ WNDCLASSEXW klass;
+ ATOM klassAtom;
+
+ klassAtom = (ATOM)GetClassInfoExW(instance, WIDGETHOST_WINDOW_CLASS, &klass);
+ if (0 != klassAtom)
+ return klassAtom;
+
+ memset(&klass, 0, sizeof(klass));
+ klass.cbSize = sizeof(klass);
+ klass.style = CS_DBLCLKS;
+ klass.lpfnWndProc = WidgetHost_WindowProc;
+ klass.cbClsExtra = 0;
+ klass.cbWndExtra = sizeof(WidgetHost*);
+ klass.hInstance = instance;
+ klass.hIcon = NULL;
+ klass.hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
+ klass.hbrBackground = NULL;
+ klass.lpszMenuName = NULL;
+ klass.lpszClassName = WIDGETHOST_WINDOW_CLASS;
+ klass.hIconSm = NULL;
+ klassAtom = RegisterClassExW(&klass);
+
+ return klassAtom;
+}
+
+
+HWND
+WidgetHost_Create(unsigned int windowStyle, int x, int y, int width, int height,
+ HWND parentWindow, WidgetCreateProc createProc, void *createParam)
+{
+ HINSTANCE instance;
+ ATOM klassAtom;
+ HWND hwnd;
+ WidgetHostCreateParam hostParam;
+
+
+ if (NULL == createProc)
+ return NULL;
+
+ instance = GetModuleHandleW(NULL);
+ klassAtom = WidgetHost_GetClassAtom(instance);
+ if (0 == klassAtom)
+ return NULL;
+
+ hostParam.widgetCreate = createProc;
+ hostParam.widgetCreateParam = createParam;
+
+
+ hwnd = CreateWindowExW(WS_EX_NOPARENTNOTIFY |WS_EX_CONTROLPARENT,
+ (LPCWSTR)MAKEINTATOM(klassAtom), NULL,
+ WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | windowStyle,
+ x, y, width, height,
+ parentWindow, NULL, instance, &hostParam);
+
+ return hwnd;
+}
+
+static void
+WidgetHost_Layout(HWND hwnd, BOOL redraw)
+{
+ WidgetHost *self;
+ RECT rect;
+ WIDGETHOST_RET_VOID(self, hwnd);
+
+ if (FALSE == GetClientRect(hwnd, &rect))
+ return;
+
+ HWND widgetWindow = WIDGETHOST_WIDGET(hwnd);
+ if (NULL != widgetWindow)
+ {
+ unsigned int flags = SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER;
+ if (FALSE == redraw)
+ flags |= SWP_NOREDRAW;
+
+ SetWindowPos(widgetWindow, NULL, 0, 0, RECTWIDTH(rect), RECTHEIGHT(rect), flags);
+ }
+}
+
+static void
+WidgetHost_Paint(HWND hwnd, HDC hdc, const RECT *paintRect, BOOL erase)
+{
+ if (FALSE != erase)
+ {
+ COLORREF backColor, prevBackColor;
+ backColor = Graphics_GetSkinColor(WADLG_WNDBG);
+ prevBackColor = SetBkColor(hdc, backColor);
+
+ ExtTextOut(hdc, 0, 0, ETO_OPAQUE, paintRect, NULL, 0, NULL);
+ SetBkColor(hdc, prevBackColor);
+ }
+}
+
+static void
+WidgetHost_UpdateSkin(HWND hwnd)
+{
+ WidgetHost *self;
+ WIDGETHOST_RET_VOID(self, hwnd);
+ BOOL styleChanged = FALSE;
+
+ if (FALSE != WidgetStyle_UpdateDefaultColors(&self->widgetStyle))
+ styleChanged = TRUE;
+
+ if (FALSE != styleChanged)
+ {
+ HWND widgetWindow = WIDGETHOST_WIDGET(hwnd);
+ if (NULL != widgetWindow)
+ {
+ WIDGET_STYLE_COLOR_CHANGED(widgetWindow);
+ InvalidateRect(widgetWindow, NULL, TRUE);
+ }
+ }
+
+}
+
+static void
+WidgetHost_UpdateFont(HWND hwnd, BOOL redraw)
+{
+ WidgetHost *self;
+ BOOL styleChanged = FALSE;
+ long unitWidth, unitHeight;
+
+ WIDGETHOST_RET_VOID(self, hwnd);
+
+ if (FALSE == Graphics_GetWindowBaseUnits(hwnd, &unitWidth, &unitHeight))
+ {
+ unitWidth = 6;
+ unitHeight = 13;
+ }
+
+ if (FALSE != WidgetStyle_UpdateDefaultFonts(&self->widgetStyle, self->font, unitWidth, unitHeight))
+ styleChanged = TRUE;
+
+ if (FALSE != styleChanged)
+ {
+ HWND widgetWindow = WIDGETHOST_WIDGET(hwnd);
+ if (NULL != widgetWindow)
+ {
+ WIDGET_STYLE_COLOR_CHANGED(widgetWindow);
+ InvalidateRect(widgetWindow, NULL, TRUE);
+ }
+ }
+
+}
+
+static LRESULT
+WidgetHost_OnCreate(HWND hwnd, CREATESTRUCT *createStruct)
+{
+ WidgetHost *self;
+ HWND widgetWindow;
+ WidgetHostCreateParam *createParam;
+
+ if (NULL == createStruct)
+ return -1;
+
+ createParam = (WidgetHostCreateParam*)createStruct->lpCreateParams;
+ if (NULL == createParam)
+ return -1;
+
+ self = (WidgetHost*)malloc(sizeof(WidgetHost));
+ if (NULL == self)
+ return -1;
+
+ SetLastError(ERROR_SUCCESS);
+ if (!SetWindowLongPtr(hwnd, 0, (LONGX86)self) && ERROR_SUCCESS != GetLastError())
+ return -1;
+
+ memset(self, 0, sizeof(WidgetHost));
+
+ WIDGETHOST_FREEZE(self);
+
+ MLSkinWindow2(Plugin_GetLibraryWindow(), hwnd, SKINNEDWND_TYPE_WINDOW,
+ SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
+
+ WidgetHost_UpdateFont(hwnd, FALSE);
+ WidgetHost_UpdateSkin(hwnd);
+
+ widgetWindow = NULL;
+ if (NULL != createParam->widgetCreate)
+ widgetWindow = createParam->widgetCreate(hwnd, createParam->widgetCreateParam);
+
+ if (NULL == widgetWindow)
+ return -1;
+
+ SetWindowLongPtrW(widgetWindow, GWLP_ID, WIDGETHOST_WIDGET_ID);
+
+ WIDGET_SET_STYLE(widgetWindow, &self->widgetStyle);
+
+ SetWindowPos(widgetWindow, NULL, 0, 0, 0, 0,
+ SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOREDRAW | SWP_FRAMECHANGED);
+
+ ShowWindow(widgetWindow, SW_SHOWNA);
+
+ SetWindowPos(widgetWindow, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
+
+ WIDGETHOST_THAW(self);
+
+ SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
+ SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_FRAMECHANGED);
+
+ return 0;
+}
+
+static void
+WidgetHost_OnDestroy(HWND hwnd)
+{
+ WidgetHost *self;
+
+ self = WIDGETHOST(hwnd);
+ SetWindowLongPtr(hwnd, 0, 0);
+
+ if (NULL == self)
+ return;
+
+ WIDGETHOST_FREEZE(self);
+
+ WidgetStyle_Free(&self->widgetStyle);
+ free(self);
+}
+
+static void
+WidgetHost_OnPaint(HWND hwnd)
+{
+ PAINTSTRUCT ps;
+
+ if (NULL != BeginPaint(hwnd, &ps))
+ {
+ WidgetHost_Paint(hwnd, ps.hdc, &ps.rcPaint, ps.fErase);
+ EndPaint(hwnd, &ps);
+ }
+}
+
+static void
+WidgetHost_OnPrintClient(HWND hwnd, HDC hdc, UINT options)
+{
+ RECT clientRect;
+ if (GetClientRect(hwnd, &clientRect))
+ {
+ WidgetHost_Paint(hwnd, hdc, &clientRect, TRUE);
+ }
+}
+
+static void
+WidgetHost_OnWindowPosChanged(HWND hwnd, WINDOWPOS *windowPos)
+{
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE | SWP_FRAMECHANGED) & windowPos->flags))
+ {
+ WidgetHost *self;
+ WIDGETHOST_RET_VOID(self, hwnd);
+
+ if (FALSE != WIDGETHOST_IS_FROZEN(self))
+ return;
+
+ WidgetHost_Layout(hwnd, 0 == (SWP_NOREDRAW & windowPos->flags));
+ }
+}
+
+static void
+WidgetHost_OnDisplayChanged(HWND hwnd, INT bpp, INT dpi_x, INT dpi_y)
+{
+ WidgetHost *self;
+ WIDGETHOST_RET_VOID(self, hwnd);
+
+ if (FALSE != WIDGETHOST_IS_FROZEN(self))
+ return;
+
+ WidgetHost_UpdateSkin(hwnd);
+ InvalidateRect(hwnd, NULL, TRUE);
+}
+
+static void
+WidgetHost_OnSetFont(HWND hwnd, HFONT font, BOOL redraw)
+{
+ WidgetHost *self;
+ LOGFONTW prevFont, newFont;
+
+ WIDGETHOST_RET_VOID(self, hwnd);
+
+ if (NULL == self->font ||
+ sizeof(LOGFONTW) != GetObjectW(self->font, sizeof(prevFont), &prevFont))
+ {
+ ZeroMemory(&prevFont, sizeof(prevFont));
+ }
+
+ self->font = font;
+
+
+ if (NULL == self->font ||
+ sizeof(newFont) != GetObjectW(self->font, sizeof(newFont), &newFont))
+ {
+ ZeroMemory(&newFont, sizeof(newFont));
+ }
+
+ if (0 != memcmp(&prevFont, &newFont, sizeof(prevFont)) &&
+ FALSE == WIDGETHOST_IS_FROZEN(self))
+ {
+ WidgetHost_UpdateFont(hwnd, redraw);
+ }
+}
+
+static HFONT
+WidgetHost_OnGetFont(HWND hwnd)
+{
+ WidgetHost *self;
+ WIDGETHOST_RET_VAL(self, hwnd, NULL);
+
+ return self->font;
+}
+
+static void
+WidgetHost_OnSetUpdateRegion(HWND hwnd, HRGN updateRegion, POINTS regionOffset)
+{
+ WidgetHost *self;
+ WIDGETHOST_RET_VOID(self, hwnd);
+
+ self->updateRegion = updateRegion;
+ self->updateOffset.x = regionOffset.x;
+ self->updateOffset.y = regionOffset.y;
+}
+
+
+static LRESULT CALLBACK
+WidgetHost_WindowProc(HWND hwnd, unsigned int uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_CREATE: return WidgetHost_OnCreate(hwnd, (CREATESTRUCT*)lParam);
+ case WM_DESTROY: WidgetHost_OnDestroy(hwnd); return 0;
+ case WM_PAINT: WidgetHost_OnPaint(hwnd); return 0;
+ case WM_PRINTCLIENT: WidgetHost_OnPrintClient(hwnd, (HDC)wParam, (UINT)lParam); return 0;
+ case WM_PRINT: return 0;
+ case WM_ERASEBKGND: return 0;
+ case WM_WINDOWPOSCHANGED: WidgetHost_OnWindowPosChanged(hwnd, (WINDOWPOS*)lParam); return 0;
+ case WM_SIZE: return 0;
+ case WM_MOVE: return 0;
+ case WM_DISPLAYCHANGE: WidgetHost_OnDisplayChanged(hwnd, (INT)wParam, LOWORD(lParam), HIWORD(lParam)); return 0;
+ case WM_SETFONT: WidgetHost_OnSetFont(hwnd, (HFONT)wParam, LOWORD(lParam)); return 0;
+ case WM_GETFONT: return (LRESULT)WidgetHost_OnGetFont(hwnd);
+
+ // gen_ml flickerless drawing
+ case WM_USER + 0x200: return 1;
+ case WM_USER + 0x201: WidgetHost_OnSetUpdateRegion(hwnd, (HRGN)lParam, MAKEPOINTS(wParam)); return 0;
+
+ }
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/widgetHost.h b/Src/Plugins/Library/ml_devices/widgetHost.h
new file mode 100644
index 00000000..b27b3ea3
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/widgetHost.h
@@ -0,0 +1,23 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_WDGET_HOST_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_WIDGET_HOST_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+typedef HWND (*WidgetCreateProc)(HWND hostWindow, void *user);
+
+HWND
+WidgetHost_Create(unsigned int windowStyle,
+ int x,
+ int y,
+ int width,
+ int height,
+ HWND parentWindow,
+ WidgetCreateProc createProc,
+ void *createParam);
+
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_WIDGET_HOST_HEADER
diff --git a/Src/Plugins/Library/ml_devices/widgetStyle.cpp b/Src/Plugins/Library/ml_devices/widgetStyle.cpp
new file mode 100644
index 00000000..1e72ec06
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/widgetStyle.cpp
@@ -0,0 +1,261 @@
+#include "main.h"
+#include "./widgetStyle.h"
+
+static void
+WidgetStyle_DeleteGdiObject(UINT flags, UINT flagsMask, HGDIOBJ object)
+{
+ if (NULL != object && 0 != (flagsMask & flags))
+ DeleteObject(object);
+}
+
+
+static BOOL
+WidgetStyle_SetBrushInt(HBRUSH *brush, WidgetStyleFlags *flags, WidgetStyleFlags flagsMask,
+ HBRUSH sourceBrush, WidgetStyleAssignFlags assignFlags)
+{
+ LOGBRUSH lb;
+
+ WidgetStyle_DeleteGdiObject(*flags, flagsMask, *brush);
+
+ if (0 == (WIDGETSTYLE_COPY_OBJECT & assignFlags))
+ {
+ if (0 == (WIDGETSTYLE_OWN_OBJECT & assignFlags))
+ *flags &= ~flagsMask;
+ else
+ *flags |= flagsMask;
+
+ *brush = sourceBrush;
+ return TRUE;
+ }
+
+
+ *brush = (sizeof(lb) == GetObjectW(sourceBrush, sizeof(lb), &lb)) ?
+ CreateBrushIndirect(&lb) : NULL;
+
+ if (NULL == *brush)
+ {
+ *flags &= ~flagsMask;
+ return FALSE;
+ }
+
+ *flags |= flagsMask;
+ return TRUE;
+}
+
+static BOOL
+WidgetStyle_SetFontInt(HFONT *font, WidgetStyleFlags *flags, WidgetStyleFlags flagsMask,
+ HFONT sourceFont, WidgetStyleAssignFlags assignFlags)
+{
+ LOGFONTW lf = {0};
+ WidgetStyle_DeleteGdiObject(*flags, flagsMask, *font);
+
+ if (0 == (WIDGETSTYLE_COPY_OBJECT & assignFlags))
+ {
+ if (0 == (WIDGETSTYLE_OWN_OBJECT & assignFlags))
+ *flags &= ~flagsMask;
+ else
+ *flags |= flagsMask;
+
+ *font = sourceFont;
+ return TRUE;
+ }
+
+ *font = (sizeof(lf) == GetObjectW(sourceFont, sizeof(lf), &lf)) ? CreateFontIndirectW(&lf) : NULL;
+
+ if (NULL == *font)
+ {
+ *flags &= ~flagsMask;
+ return FALSE;
+ }
+
+ *flags |= flagsMask;
+ return TRUE;
+}
+
+void
+WidgetStyle_Free(WidgetStyle *self)
+{
+ if (NULL == self)
+ return;
+
+ WidgetStyle_DeleteGdiObject(self->flags, WIDGETSTYLE_OWN_TEXT_FONT, self->textFont);
+ WidgetStyle_DeleteGdiObject(self->flags, WIDGETSTYLE_OWN_TITLE_FONT, self->titleFont);
+ WidgetStyle_DeleteGdiObject(self->flags, WIDGETSTYLE_OWN_CATEGORY_FONT, self->categoryFont);
+ WidgetStyle_DeleteGdiObject(self->flags, WIDGETSTYLE_OWN_BACK_BRUSH, self->backBrush);
+ WidgetStyle_DeleteGdiObject(self->flags, WIDGETSTYLE_OWN_CATEGORY_BRUSH, self->categoryBrush);
+}
+
+BOOL
+WidgetStyle_SetBackBrush(WidgetStyle *self, HBRUSH brush, WidgetStyleAssignFlags flags)
+{
+ return WidgetStyle_SetBrushInt(&self->backBrush, &self->flags,
+ WIDGETSTYLE_OWN_BACK_BRUSH, brush, flags);
+}
+
+BOOL
+WidgetStyle_SetCategoryBrush(WidgetStyle *self, HBRUSH brush, WidgetStyleAssignFlags flags)
+{
+ return WidgetStyle_SetBrushInt(&self->categoryBrush, &self->flags,
+ WIDGETSTYLE_OWN_CATEGORY_BRUSH, brush, flags);
+}
+
+BOOL
+WidgetStyle_SetTextFont(WidgetStyle *self, HFONT font, WidgetStyleAssignFlags flags)
+{
+ return WidgetStyle_SetFontInt(&self->textFont, &self->flags,
+ WIDGETSTYLE_OWN_TEXT_FONT, font, flags);
+
+}
+
+BOOL
+WidgetStyle_SetTitleFont(WidgetStyle *self, HFONT font, WidgetStyleAssignFlags flags)
+{
+ return WidgetStyle_SetFontInt(&self->titleFont, &self->flags,
+ WIDGETSTYLE_OWN_TITLE_FONT, font, flags);
+
+}
+
+BOOL
+WidgetStyle_SetCategoryFont(WidgetStyle *self, HFONT font, WidgetStyleAssignFlags flags)
+{
+ return WidgetStyle_SetFontInt(&self->categoryFont, &self->flags,
+ WIDGETSTYLE_OWN_CATEGORY_FONT, font, flags);
+
+}
+
+static COLORREF
+WidgetStyle_GetCategoryLineColor(COLORREF categoryBackColor)
+{
+ COLORREF categoryLineColor;
+ size_t index;
+
+ const int categoryLineColors[] =
+ {
+ WADLG_LISTHEADER_FRAME_MIDDLECOLOR,
+ WADLG_LISTHEADER_FRAME_BOTTOMCOLOR,
+ WADLG_LISTHEADER_FRAME_TOPCOLOR,
+ WADLG_HILITE,
+ };
+
+ for (index = 0; index < ARRAYSIZE(categoryLineColors); index++)
+ {
+ categoryLineColor = Graphics_GetSkinColor(categoryLineColors[index]);
+ int distance = Graphics_GetColorDistance(categoryLineColor, categoryBackColor);
+ if (distance < 0) distance = -distance;
+ if (distance >= 40) break;
+ }
+
+ return categoryLineColor;
+}
+
+static COLORREF
+WidgetStyle_GetBorderColor(COLORREF backColor, COLORREF textColor)
+{
+ COLORREF borderColor;
+
+ for(int step = 0;; step++)
+ {
+ switch(step)
+ {
+ case 0:
+ borderColor = Graphics_GetSkinColor(WADLG_HILITE);
+ break;
+ case 1:
+ borderColor = Graphics_BlendColors(Graphics_GetSkinColor(WADLG_SELBAR_FGCOLOR), Graphics_GetSkinColor(WADLG_SELBAR_BGCOLOR), 17);
+ borderColor = Graphics_BlendColors(borderColor, backColor, 229);
+ break;
+ default:
+ return textColor;
+ }
+
+ int distance = Graphics_GetColorDistance(borderColor, backColor);
+ if (distance < 0) distance = -distance;
+ if (distance >= 40)
+ break;
+ }
+
+ return borderColor;
+}
+
+BOOL
+WidgetStyle_UpdateDefaultColors(WidgetStyle *style)
+{
+ #define WIDGETSTYLE_SET_COLOR(_colorName, _color)\
+ {COLORREF _colorCopy = (_color);\
+ if (WIDGETSTYLE_##_colorName##_COLOR(style) != _colorCopy)\
+ {WIDGETSTYLE_SET_##_colorName##_COLOR(style, _colorCopy);\
+ styleChanged = TRUE;}}
+
+ #define WIDGETSTYLE_SET_COLOR_BLEND(_colorName, _colorTop, _colorBottom, _alpha)\
+ WIDGETSTYLE_SET_COLOR(_colorName, Graphics_BlendColors((_colorTop), (_colorBottom), (_alpha)))
+
+
+ COLORREF widgetBackColor, widgetTextColor, categoryBackColor, categoryTextColor;
+ BOOL styleChanged;
+ HBRUSH brush;
+
+ if (NULL == style)
+ return FALSE;
+
+ styleChanged = FALSE;
+
+ widgetBackColor = Graphics_GetSkinColor(WADLG_ITEMBG);
+ widgetTextColor = Graphics_GetSkinColor(WADLG_ITEMFG);
+ categoryBackColor = Graphics_GetSkinColor(WADLG_LISTHEADER_BGCOLOR);
+ categoryTextColor = Graphics_GetSkinColor(WADLG_LISTHEADER_FONTCOLOR);
+
+ if (WIDGETSTYLE_BACK_COLOR(style) != widgetBackColor ||
+ NULL == WIDGETSTYLE_BACK_BRUSH(style))
+ {
+ brush = CreateSolidBrush(widgetBackColor);
+ WIDGETSTYLE_SET_BACK_BRUSH(style, brush, WIDGETSTYLE_OWN_OBJECT);
+ styleChanged = TRUE;
+ }
+
+ if (WIDGETSTYLE_CATEGORY_BACK_COLOR(style) != categoryBackColor ||
+ NULL == WIDGETSTYLE_CATEGORY_BRUSH(style))
+ {
+ brush = CreateSolidBrush(categoryBackColor);
+ WIDGETSTYLE_SET_CATEGORY_BRUSH(style, brush, WIDGETSTYLE_OWN_OBJECT);
+ styleChanged = TRUE;
+ }
+
+ WIDGETSTYLE_SET_COLOR(BACK, widgetBackColor);
+ WIDGETSTYLE_SET_COLOR(TEXT, widgetTextColor);
+ WIDGETSTYLE_SET_COLOR_BLEND(TITLE, widgetTextColor, widgetBackColor, 210);
+ WIDGETSTYLE_SET_COLOR(BORDER, WidgetStyle_GetBorderColor(widgetBackColor, widgetTextColor));
+ WIDGETSTYLE_SET_COLOR(IMAGE_BACK, widgetBackColor);
+ WIDGETSTYLE_SET_COLOR(IMAGE_FRONT, widgetTextColor);
+ WIDGETSTYLE_SET_COLOR(SELECT_BACK, Graphics_GetSkinColor(WADLG_SELBAR_BGCOLOR));
+ WIDGETSTYLE_SET_COLOR(SELECT_FRONT, Graphics_GetSkinColor(WADLG_SELBAR_FGCOLOR));
+ WIDGETSTYLE_SET_COLOR_BLEND(INACTIVE_SELECT_BACK, WIDGETSTYLE_SELECT_BACK_COLOR(style), widgetBackColor, 192);
+ WIDGETSTYLE_SET_COLOR_BLEND(INACTIVE_SELECT_FRONT, WIDGETSTYLE_SELECT_FRONT_COLOR(style), widgetBackColor, 192);
+ WIDGETSTYLE_SET_COLOR(CATEGORY_BACK, categoryBackColor);
+ WIDGETSTYLE_SET_COLOR(CATEGORY_TEXT, categoryTextColor);
+ WIDGETSTYLE_SET_COLOR(CATEGORY_LINE, WidgetStyle_GetCategoryLineColor(categoryBackColor));
+ WIDGETSTYLE_SET_COLOR_BLEND(CATEGORY_EMPTY_TEXT, widgetTextColor, widgetBackColor, 210);
+ WIDGETSTYLE_SET_COLOR_BLEND(TEXT_EDITOR_BORDER, widgetTextColor, widgetBackColor, 140);
+
+ return styleChanged;
+}
+
+BOOL
+WidgetStyle_UpdateDefaultFonts(WidgetStyle *style, HFONT baseFont, long unitWidth, long unitHeight)
+{
+ HFONT tempFont;
+
+ if (NULL == style)
+ return FALSE;
+
+ tempFont = Graphics_DuplicateFont(baseFont, 3, FALSE, TRUE);
+ WIDGETSTYLE_SET_TITLE_FONT(style, tempFont, WIDGETSTYLE_OWN_OBJECT);
+
+ tempFont = Graphics_DuplicateFont(baseFont, 0, FALSE, TRUE);
+ WIDGETSTYLE_SET_CATEGORY_FONT(style, tempFont, WIDGETSTYLE_OWN_OBJECT);
+
+ WIDGETSTYLE_SET_TEXT_FONT(style, baseFont, WIDGETSTYLE_LINK_OBJECT);
+
+ WIDGETSTYLE_SET_UNIT_SIZE(style, unitWidth, unitHeight);
+
+ return TRUE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_devices/widgetStyle.h b/Src/Plugins/Library/ml_devices/widgetStyle.h
new file mode 100644
index 00000000..bb2a010a
--- /dev/null
+++ b/Src/Plugins/Library/ml_devices/widgetStyle.h
@@ -0,0 +1,159 @@
+#ifndef _NULLSOFT_WINAMP_ML_DEVICES_WIDGETSTYLE_HEADER
+#define _NULLSOFT_WINAMP_ML_DEVICES_WIDGETSTYLE_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+typedef struct WidgetStyle WidgetStyle;
+typedef enum WidgetStyleFlags WidgetStyleFlags;
+typedef enum WidgetStyleAssignFlags WidgetStyleAssignFlags;
+
+enum WidgetStyleFlags
+{
+ WIDGETSTYLE_OWN_TEXT_FONT = (1 << 0),
+ WIDGETSTYLE_OWN_TITLE_FONT = (1 << 1),
+ WIDGETSTYLE_OWN_CATEGORY_FONT = (1 << 2),
+ WIDGETSTYLE_OWN_BACK_BRUSH = (1 << 3),
+ WIDGETSTYLE_OWN_CATEGORY_BRUSH = (1 << 4),
+};
+DEFINE_ENUM_FLAG_OPERATORS(WidgetStyleFlags);
+
+enum WidgetStyleAssignFlags
+{
+ WIDGETSTYLE_LINK_OBJECT = 0,
+ WIDGETSTYLE_COPY_OBJECT = (1 << 0),
+ WIDGETSTYLE_OWN_OBJECT = (1 << 1),
+};
+DEFINE_ENUM_FLAG_OPERATORS(WidgetStyleAssignFlags);
+
+
+struct WidgetStyle
+{
+ WidgetStyleFlags flags;
+ HFONT textFont;
+ HFONT titleFont;
+ HFONT categoryFont;
+ HBRUSH backBrush;
+ HBRUSH categoryBrush;
+ COLORREF titleColor;
+ COLORREF textColor;
+ COLORREF backColor;
+ COLORREF borderColor;
+ COLORREF imageBackColor;
+ COLORREF imageFrontColor;
+ COLORREF selectBackColor;
+ COLORREF selectFrontColor;
+ COLORREF inactiveSelectBackColor;
+ COLORREF inactiveSelectFrontColor;
+ COLORREF categoryTextColor;
+ COLORREF categoryLineColor;
+ COLORREF categoryBackColor;
+ COLORREF categoryEmptyTextColor;
+ COLORREF textEditorBorderColor;
+ SIZE unitSize;
+};
+
+#define DLU_TO_PX_VALIDATE_MIN(_value, _dlu, _min)\
+ {if (0 != (_dlu) && ((_value) < (_min))) (_value) = (_min);}
+
+#define WIDGETSTYLE_DLU_TO_HORZ_PX(_style, _dlu) MulDiv((_dlu), ((WidgetStyle*)(_style))->unitSize.cx, 4)
+#define WIDGETSTYLE_DLU_TO_VERT_PX(_style, _dlu) MulDiv((_dlu), ((WidgetStyle*)(_style))->unitSize.cy, 8)
+
+#define WIDGETSTYLE_DLU_TO_HORZ_PX_MIN(_value, _style, _dlu, _min)\
+ {_value = WIDGETSTYLE_DLU_TO_HORZ_PX(_style, _dlu); DLU_TO_PX_VALIDATE_MIN(_value, _dlu, _min);}
+
+#define WIDGETSTYLE_DLU_TO_VERT_PX_MIN(_value, _style, _dlu, _min)\
+ {_value = WIDGETSTYLE_DLU_TO_VERT_PX(_style, _dlu); DLU_TO_PX_VALIDATE_MIN(_value, _dlu, _min);}
+
+#define WIDGETSTYLE_TITLE_FONT(_style) (((WidgetStyle*)(_style))->titleFont)
+#define WIDGETSTYLE_TEXT_FONT(_style) (((WidgetStyle*)(_style))->textFont)
+#define WIDGETSTYLE_CATEGORY_FONT(_style) (((WidgetStyle*)(_style))->categoryFont)
+#define WIDGETSTYLE_BACK_BRUSH(_style) (((WidgetStyle*)(_style))->backBrush)
+#define WIDGETSTYLE_CATEGORY_BRUSH(_style) (((WidgetStyle*)(_style))->categoryBrush)
+#define WIDGETSTYLE_TITLE_COLOR(_style) (((WidgetStyle*)(_style))->titleColor)
+#define WIDGETSTYLE_TEXT_COLOR(_style) (((WidgetStyle*)(_style))->textColor)
+#define WIDGETSTYLE_BACK_COLOR(_style) (((WidgetStyle*)(_style))->backColor)
+#define WIDGETSTYLE_BORDER_COLOR(_style) (((WidgetStyle*)(_style))->borderColor)
+#define WIDGETSTYLE_IMAGE_BACK_COLOR(_style) (((WidgetStyle*)(_style))->imageBackColor)
+#define WIDGETSTYLE_IMAGE_FRONT_COLOR(_style) (((WidgetStyle*)(_style))->imageFrontColor)
+#define WIDGETSTYLE_SELECT_BACK_COLOR(_style) (((WidgetStyle*)(_style))->selectBackColor)
+#define WIDGETSTYLE_SELECT_FRONT_COLOR(_style) (((WidgetStyle*)(_style))->selectFrontColor)
+#define WIDGETSTYLE_INACTIVE_SELECT_BACK_COLOR(_style) (((WidgetStyle*)(_style))->inactiveSelectBackColor)
+#define WIDGETSTYLE_INACTIVE_SELECT_FRONT_COLOR(_style) (((WidgetStyle*)(_style))->inactiveSelectFrontColor)
+#define WIDGETSTYLE_CATEGORY_TEXT_COLOR(_style) (((WidgetStyle*)(_style))->categoryTextColor)
+#define WIDGETSTYLE_CATEGORY_BACK_COLOR(_style) (((WidgetStyle*)(_style))->categoryBackColor)
+#define WIDGETSTYLE_CATEGORY_LINE_COLOR(_style) (((WidgetStyle*)(_style))->categoryLineColor)
+#define WIDGETSTYLE_CATEGORY_EMPTY_TEXT_COLOR(_style) (((WidgetStyle*)(_style))->categoryEmptyTextColor)
+#define WIDGETSTYLE_TEXT_EDITOR_BORDER_COLOR(_style) (((WidgetStyle*)(_style))->textEditorBorderColor)
+#define WIDGETSTYLE_SET_UNIT_SIZE(_style, _width, _height)\
+ {(((WidgetStyle*)(_style))->unitSize).cx = _width;\
+ (((WidgetStyle*)(_style))->unitSize).cy = _height;}
+
+#define WIDGETSTYLE_SET_TITLE_FONT(_style, _font, _flags)\
+ WidgetStyle_SetTitleFont(((WidgetStyle*)(_style)), (_font), (_flags))
+#define WIDGETSTYLE_SET_TEXT_FONT(_style, _font, _flags)\
+ WidgetStyle_SetTextFont(((WidgetStyle*)(_style)), (_font), (_flags))
+#define WIDGETSTYLE_SET_CATEGORY_FONT(_style, _font, _flags)\
+ WidgetStyle_SetCategoryFont(((WidgetStyle*)(_style)), (_font), (_flags))
+#define WIDGETSTYLE_SET_BACK_BRUSH(_style, _brush, _flags)\
+ WidgetStyle_SetBackBrush(((WidgetStyle*)(_style)), (_brush), (_flags))
+#define WIDGETSTYLE_SET_CATEGORY_BRUSH(_style, _brush, _flags)\
+ WidgetStyle_SetCategoryBrush(((WidgetStyle*)(_style)), (_brush), (_flags))
+
+#define WIDGETSTYLE_SET_TITLE_COLOR(_style, _color) (((WidgetStyle*)(_style))->titleColor = (_color))
+#define WIDGETSTYLE_SET_TEXT_COLOR(_style, _color) (((WidgetStyle*)(_style))->textColor = (_color))
+#define WIDGETSTYLE_SET_BACK_COLOR(_style, _color) (((WidgetStyle*)(_style))->backColor = (_color))
+#define WIDGETSTYLE_SET_BORDER_COLOR(_style, _color) (((WidgetStyle*)(_style))->borderColor = (_color))
+#define WIDGETSTYLE_SET_IMAGE_BACK_COLOR(_style, _color) (((WidgetStyle*)(_style))->imageBackColor = (_color))
+#define WIDGETSTYLE_SET_IMAGE_FRONT_COLOR(_style, _color) (((WidgetStyle*)(_style))->imageFrontColor = (_color))
+#define WIDGETSTYLE_SET_SELECT_BACK_COLOR(_style, _color) (((WidgetStyle*)(_style))->selectBackColor = (_color))
+#define WIDGETSTYLE_SET_SELECT_FRONT_COLOR(_style, _color) (((WidgetStyle*)(_style))->selectFrontColor = (_color))
+#define WIDGETSTYLE_SET_INACTIVE_SELECT_BACK_COLOR(_style, _color) (((WidgetStyle*)(_style))->inactiveSelectBackColor = (_color))
+#define WIDGETSTYLE_SET_INACTIVE_SELECT_FRONT_COLOR(_style, _color) (((WidgetStyle*)(_style))->inactiveSelectFrontColor = (_color))
+#define WIDGETSTYLE_SET_CATEGORY_TEXT_COLOR(_style, _color) (((WidgetStyle*)(_style))->categoryTextColor = (_color))
+#define WIDGETSTYLE_SET_CATEGORY_BACK_COLOR(_style, _color) (((WidgetStyle*)(_style))->categoryBackColor = (_color))
+#define WIDGETSTYLE_SET_CATEGORY_LINE_COLOR(_style, _color) (((WidgetStyle*)(_style))->categoryLineColor = (_color))
+#define WIDGETSTYLE_SET_CATEGORY_EMPTY_TEXT_COLOR(_style, _color) (((WidgetStyle*)(_style))->categoryEmptyTextColor = (_color))
+#define WIDGETSTYLE_SET_TEXT_EDITOR_BORDER_COLOR(_style, _color) (((WidgetStyle*)(_style))->textEditorBorderColor = (_color))
+void
+WidgetStyle_Free(WidgetStyle *self);
+
+BOOL
+WidgetStyle_SetBackBrush(WidgetStyle *self,
+ HBRUSH brush,
+ WidgetStyleAssignFlags flags);
+
+BOOL
+WidgetStyle_SetCategoryBrush(WidgetStyle *self,
+ HBRUSH brush,
+ WidgetStyleAssignFlags flags);
+
+BOOL
+WidgetStyle_SetTextFont(WidgetStyle *self,
+ HFONT font,
+ WidgetStyleAssignFlags flags);
+
+BOOL
+WidgetStyle_SetTitleFont(WidgetStyle *self,
+ HFONT font,
+ WidgetStyleAssignFlags flags);
+
+BOOL
+WidgetStyle_SetCategoryFont(WidgetStyle *self,
+ HFONT font,
+ WidgetStyleAssignFlags flags);
+
+BOOL
+WidgetStyle_UpdateDefaultColors(WidgetStyle *style);
+
+BOOL
+WidgetStyle_UpdateDefaultFonts(WidgetStyle *style,
+ HFONT baseFont,
+ long unitWidth,
+ long unitHeight);
+
+
+#endif //_NULLSOFT_WINAMP_ML_DEVICES_WIDGETSTYLE_HEADER